#!/usr/bin/python

import cStringIO
import StringIO
import logging
import mox
import os
import pprint
import re
import sys

import metameta
metameta.Metastrap()

from wmh import testing

import meta.compiler.errors
import parser
from auto import bootstrap

# These rely on auto/bootstrap.py
BOOTSTRAP_SCHEMA = bootstrap.BootstrapMeta(parser.GenericConstruct)
BOOTSTRAP_OOPL = bootstrap.BootstrapOopl(parser.GenericConstruct)


class TestCase(testing.TestCase):

  def setUp(self):
    super(TestCase, self).setUp()

    self.maxDiff = None

    # This module-global variable needs to be reset each time, in case we've
    # overwritten it with a memory-specific instance.  However, it is each
    # unittest's responsibility to ensure that if a memory-based instance
    # is created, that undoMemory() is always called on it before the test
    # ends.
    parser.IO = parser.Filesystem(memory=False)

    self._filename = 'testdata/meta.meta'
    self._filename2 = 'testdata/ex.meta'
    self._constructs = BOOTSTRAP_SCHEMA
    self._context = parser.Context(None, self._constructs)
    self._context2 = parser.Context(None, self._constructs + BOOTSTRAP_OOPL)
    self._metac = parser.Compiler(metal='oopl')
    self._oopl = self._metac.metalangNamed('oopl')

    self._parser = parser.MetaFile(
      self._filename, self._context, compiler=self._metac, destdir='/tmp')

    self._ooplpy = self._oopl.baselangNamed('python')
    self._parser2 = parser.MetaFile(self._filename2, self._context2)

    # We intentionally do NOT register cons with block or primary with cons,
    # so that we can test before/after registration in various test methods.
    self._block = parser.ComplexBlockAttribute('scope', [], line=3)
    self._cons = parser.Construct('age')
    self._ccons = parser.GenericConstruct('class', 'A')
    self._mcons = parser.GenericConstruct('method', 'func')
    self._primary = parser.IdAttribute('method', 'func', line=1)
    self._scope = parser.ComplexBlockAttribute('scope', [], line=1)

    # Why does this not get reset each method call?  Without clearing it,
    # testing MetaLanguageTest causes CompilerTest.test_schema to fail.
    #    % ptest MetaLanguageTest CompilerTest
    self._oopl._schema_construct = None

    self._oopl_cons = [
      'accessor', 'assoc', 'class', 'field', 'initializer', 'method',
      'namespace', 'native', 'var']

  def prepare(self, filename, destdir, debug_level=0, parse=True):
    """Create the core instances needed to parse/compile/etc a given meta file.

    Args:
      filename: str
        The path to the meta file to prepare.
      destdir: str
      debug_level: int
      parse: bool
        If False, do not parse the file.

    Returns: parser.FileEnv
    """
    metac = parser.Compiler('oopl')
    oopl = metac.metalangNamed('oopl')
    context = parser.Context(None, BOOTSTRAP_SCHEMA + BOOTSTRAP_OOPL)
    prs = parser.MetaFile(
      filename, context, compiler=metac, destdir=destdir,
      debug_level=debug_level)
    ooplpy = self._ooplpy  # parser.OoplPython(oopl)
    #env = parser.FileEnv(prs, metac, destdir)
    if parse:
      filecons = prs.parseFile()
      self.assertNotEqual(None, filecons, 'Failed to parse %s' % filename)
    else:
      filecons = None
    return prs, filecons

  def find(self, prs, construct, spec):
    """Find a subconstruct within a construct by special specification.

    Args:
      prs: parser.MetaFile
        Provides access to useful info
      construct: parser.Construct
        The construct within which to search
      spec: str
        A dot-separated list of ids to find in nested scope attributes.

    Returns: Construct or None
    """
    context = prs.context()
    parts = spec.split('.')
    result = construct
    for cid in parts:
      result = result.scope(context).cons(cid)
      if not result:
        break
    return result


class MetaTest(TestCase):

  def setUp(self):
    super(MetaTest, self).setUp()
    self._meta = parser.Meta()

  def test_rootDir(self):
    self.assertEqual(
      os.getenv('METAROOT'), self._meta.rootDir())

  def test_metaPath(self):
    subpath = 'src/templates/meta-mode-template.el'
    self.assertEqual(
      os.path.join(os.getenv('METAROOT'), subpath),
      self._meta.metaPath(subpath))


class LiteralListTest(TestCase):

  TEST_RE = re.compile('^(\s)+(\S+)')

  def setUp(self):
    super(LiteralListTest, self).setUp()
    self._lit1 = parser.LiteralList('List<a b c>')
    self._lit2 = parser.LiteralList('List|<a|b|c>')
    self._lit3 = parser.LiteralList('List,<a,b,c>')
    self._lit4 = parser.LiteralList('List|<>')

  def test_delim(self):
    self.assertEqual(' ', self._lit1.delim())
    self.assertEqual('|', self._lit2.delim())
    self.assertEqual(',', self._lit3.delim())

  def test_data(self):
    data = ['a', 'b', 'c']
    self.assertEqual(data, self._lit1.data())
    self.assertEqual(data, self._lit2.data())
    self.assertEqual(data, self._lit3.data())

  def test___init__(self):
    data = ['a', 'b', 'c']
    self.assertEqual(data, self._lit1.data())
    self.assertEqual(data, self._lit2.data())
    self.assertEqual(data, self._lit3.data())
    self.assertEqual([], self._lit4.data())

  def test_StartsList(self):
    self.assertTrue(parser.LiteralList.StartsList('List|'))
    self.assertTrue(parser.LiteralList.StartsList('List<'))
    self.assertTrue(parser.LiteralList.StartsList('List,'))
    self.assertFalse(parser.LiteralList.StartsList('List'))
    self.assertFalse(parser.LiteralList.StartsList('Lisf'))

  def test___str__(self):
    self.assertEqual('List<a b c>', str(self._lit1))
    self.assertEqual('List|<a|b|c>', str(self._lit2))
    self.assertEqual('List,<a,b,c>', str(self._lit3))

  def test___repr__(self):
    self.assertEqual('LiteralList=List<a b c>', repr(self._lit1))
    self.assertEqual('LiteralList=List|<a|b|c>', repr(self._lit2))
    self.assertEqual('LiteralList=List,<a,b,c>', repr(self._lit3))


class TypeTest(TestCase):

  def test_prefix(self):
    self.assertEqual('@', self._type.prefix())

  def test_base(self):
    self.assertEqual('int', self._type.base())

  def test_native(self):
    self.assertEqual(False, self._type.native())

  def test_params(self):
    self.assertEqual(['32'], self._type.params())

  def test_raw(self):
    self.assertEqual('@int<32>', self._type.raw())

  def setUp(self):
    super(TypeTest, self).setUp()
    parser.Type.Repository = {}
    self._type = parser.Type('@int<32>', '@', 'int', ['32'])

  def test___init__(self):
    self.assertEqual('@int<32>', self._type.raw())

  def test_Instance(self):
    self.assertEqual({}, parser.Type.Repository)

    # The following types should all be valid and verbatim
    for mtstr in ('*str',
                  'void',
                  '*map<*str,@int>',
                  '*tuple<&#int,#&int,*#int,#*int,@#int,#@int>',
                  ):
      typ = parser.Type.Instance(mtstr)
      self.assertTrue(typ.isValid())
      self.assertEqual(mtstr, str(typ))

    # The following types are valid but involve a rewrite.
    for mtstr, canonical in (
      ('int', '@int'),
      ('str', '*str'),
      ('Person', '*Person'),
      ('map<int,str,Person>', '*map<@int,*str,*Person>')
      ):
      typ = parser.Type.Instance(mtstr)
      self.assertTrue(typ.isValid())
      self.assertEqual(canonical, str(typ))

    # The following types are invalid
    for mtstr in (
      'int@',
      ):
      typ = parser.Type.Instance(mtstr, allow_invalid=True)
      self.assertEqual(False, typ.isValid())

  def test_ParseTypes(self):
    self.assertEqual({}, parser.Type.Repository)
    res = parser.Type.ParseTypes('*str,@int')
    self.assertEqual(['*str', '@int'], [t.raw() for t in res])
    self.assertEqual(['*str', '@int'], sorted(parser.Type.Repository.keys()))
    res2 = parser.Type.ParseTypes('*map<*str,@int>')
    self.assertEqual(1, len(res2))
    params = res2[0].params()
    self.assertEqual(['*str', '@int'], [t.raw() for t in params])
    self.assertTrue(params[0] is parser.Type.Repository['*str'])
    self.assertTrue(params[1] is parser.Type.Repository['@int'])

  def test_isValid(self):
    t1 = parser.Type.Instance('*str')
    self.assertEqual(True, t1.isValid())
    t2 = parser.Type.Instance('@@blah', allow_invalid=True)
    self.assertEqual(False, t2.isValid())

  def test___str__(self):
    t1 = parser.Type.Instance('*str')
    self.assertEqual('*str', str(t1))


class VarSetTest(TestCase):

  def setUp(self):
    super(VarSetTest, self).setUp()
    self._varset = parser.VarSet()
    self._varset.addVar('age', 25)
    self._varset.addVar('name', 'Bob', attribute=None)
    self._varset2 = parser.VarSet(items=(('age', 25), ('name', 'Bob', None)))

  def test_addVar(self):
    self.assertEqual(['age', 'name'], sorted(self._varset._map))

  def test___contains__(self):
    self.assertEqual(True, 'age' in self._varset)
    self.assertEqual(False, 'height' in self._varset)

  def test_get(self):
    self.assertEqual(['Bob', None, None, None], self._varset.get('name'))
    self.assertEqual([None]*4, self._varset.get('height'))

  def test_interpolate(self):
    varset = self._varset
    varset.addVar('misc', 'p=%(person)s a=%(age)d')
    self.assertEqual(
      'p=%(person)s a=%(age)d', varset.get('misc')[0])
    self.assertRaises(KeyError, varset.interpolate, 'misc', {'person': 'bob'})
    varset.interpolate('misc', {'person': 'bob', 'age': 25})
    self.assertEqual('p=bob a=25', varset.get('misc')[0])

  def test_asStr(self):
    self.assertMultiLineEqual(
      'age             = 25\n'
      'name            = Bob',
      self._varset.asStr(None))


class FilesystemTest(testing.TestCase):

  def setUp(self):
    super(FilesystemTest, self).setUp()
    self._metac = parser.Compiler(metal='oopl')
    self._oopl = self._metac.metalangNamed('oopl') 
    self._ooplpy = self._oopl.baselangNamed('python')
    self._ooplcpp = self._oopl.baselangNamed('C++')
    self._io = parser.Filesystem(memory=False)

    # TODO(wmh): 
    thirdparty = os.path.join(
      os.getenv(
        'HOME'), 'src', 'svn', 'wmh', 'lib', 'python', 'wmh', 'thirdparty')
    if thirdparty not in sys.path:
      sys.path.insert(0, thirdparty)

  def test_useMemory(self):
    pass

  def test_undoMemory(self):
    pass

  def test_reader(self):
    ###
    # First we test the Filesystem wrapper to on-disk functionality.
    # Easy and straightforward.
    io = self._io
    self.assertEqual({}, io._disk_filesystem)
    fp = io.reader('testdata/tiny.py')
    self.assertEqual(file, fp.__class__)
    self.assertEqual('import re\n', fp.readline())
    self.assertEqual('import sys\n', fp.readline())
    self.assertEqual({'testdata/tiny.py': True}, io._disk_filesystem)

    self.assertFalse(fp.closed)
    io.close(fp)
    self.assertTrue(fp.closed)    

    ###
    # Now for the fake_filesystem version, where we modify the symbol tables
    # of various modules, replacing file-related functionality with a
    # full-fledged fake filesystem.  It is very important to make sure we
    # properly return the module symbol tables to their original versions
    # at the end of these tests!
    io3 = parser.Filesystem(memory=True)
    try:
      # Verify that we are using a fake os emulation
      self.assertEqual('FakeOsModule', parser.os.__class__.__name__)

      # Even though testdata/tiny.py exists in our real filesystem, it does not
      # exist in our fake filesystem, so an attempt to read from it should
      # raise IOError.
      self.assertRaises(IOError, io3.reader, 'testdata/tiny.py')

      # But if we add testdata/tiny.py to our fakefilesystem, we can then read
      # it properly.
      io3.addFakeFile(
        'testdata/tiny.py', 'import re\nimport sys\n')
      self.assertEqual({}, io3._disk_filesystem)
      fp = io3.reader('testdata/tiny.py')
      self.assertEqual('FakeFileWrapper', fp.__class__.__name__)
      self.assertEqual('import re\n', fp.readline())
      self.assertEqual('import sys\n', fp.readline())
      self.assertEqual({'testdata/tiny.py': True}, io3._disk_filesystem)

      # NOTE(wmh): fake_filesystem does not properly emulate the 'closed'
      # field on filehandles.
      self.assertFalse(fp.closed)
      io3.close(fp)
      self.assertTrue(fp.closed)

    finally:
      io3.undoMemory()

      # Verify that we've reinstituted the original os module.
      self.assertEqual('module', parser.os.__class__.__name__)
    
  def test_writer(self):
    ###
    # First we test the Filesystem wrapper to on-disk functionality.
    # Easy and straightforward.
    io = self._io
    tmpfile = 'testdata/__tmpfile__'
    try:
      self.assertFalse(os.path.exists(tmpfile))
      self.assertEqual({}, io._disk_filesystem)
      fp = io.writer(tmpfile)
      self.assertEqual({tmpfile: True}, io._disk_filesystem)
      self.assertEqual(file, fp.__class__)
      self.assertTrue(os.path.exists(tmpfile))
      fp.write('hello\n')
      fp.write('goodbye')

      self.assertFalse(fp.closed)      
      io.close(fp)
      self.assertTrue(fp.closed)

      self.assertEqual({tmpfile: True}, io._disk_filesystem)      
      
      with open(tmpfile, 'r') as tfp:
        self.assertEqual('hello\ngoodbye', tfp.read())
        
    finally:
      os.unlink(tmpfile)

    ###
    # Now for the fake_filesystem version, where we modify the symbol tables
    # of various modules, replacing file-related functionality with a
    # full-fledged fake filesystem.  It is very important to make sure we
    # properly return the module symbol tables to their original versions
    # at the end of these tests!
    io3 = parser.Filesystem(memory=True)
    try:
      # Verify that we are using a fake open emulation
      self.assertEqual('FakeFileOpen', parser.open.__class__.__name__)

      # Verify that testdata/fake.py does not exist in our fake filesystem.
      # Then create 'testdata', and write to 'testdata/fake.py'.
      path = 'testdata/fake.py'
      self.assertFalse(io3._fake_filesystem.Exists(path))
      io3.addFakeDir('testdata')
      fp = io3.writer(path)
      fp.write('hello world\n')
      fp.write('how are you?\nending')
      self.assertFalse(fp.closed)
      io3.close(fp)
      self.assertTrue(fp.closed)

      # Verify that our fake file has what we wrote to it.
      self.assertEqual(
        'hello world\nhow are you?\nending', io3.getFakeFile(path).contents)

    finally:
      io3.undoMemory()
      self.assertEqual('open', parser.open.__name__)
      self.assertEqual(
        'builtin_function_or_method', parser.open.__class__.__name__)

  def test_close(self):
    # noop - tested in reader() and writer()
    pass

  def test_exists(self):
    ###
    # First we test the Filesystem wrapper to on-disk functionality.
    # Easy and straightforward.
    io = self._io
    self.assertTrue(io.exists('testdata/tiny.py'))
    self.assertFalse(io.exists('not-a-file'))

    ###
    # Now for the fake_filesystem version, where we modify the symbol tables
    # of various modules, replacing file-related functionality with a
    # full-fledged fake filesystem.  It is very important to make sure we
    # properly return the module symbol tables to their original versions
    # at the end of these tests!
    io3 = parser.Filesystem(memory=True)
    try:
      # Ensure that tempfile has been mocked out.
      self.assertEqual('FakeTempfileModule', parser.tempfile.__class__.__name__)

      path = 'testdata/fake.py'
      io3.addFakeDir('testdata')
      self.assertFalse(io3.exists(path))
      io3.close(io3.writer(path))
      self.assertTrue(io3.exists(path))

    finally:
      io3.undoMemory()
      # Ensure that tempfile has been reinstated.
      self.assertEqual('tempfile', parser.tempfile.__name__)
      self.assertEqual('module', parser.tempfile.__class__.__name__)

  def test_mkdir(self):
    pass

  def test_makedirs(self):
    pass

  def test_listdir(self):
    ###
    # First we test the Filesystem wrapper to on-disk functionality.
    # Easy and straightforward.
    io = self._io
    files = io.listdir('./testdata')
    for f in ('Makefile', 'README', 'tiny.py', 'ex.meta'):
      self.assertTrue(f in files)
    self.assertFalse('not-a-file' in files)

    ###
    # Now for the fake_filesystem version, where we modify the symbol tables
    # of various modules, replacing file-related functionality with a
    # full-fledged fake filesystem.  It is very important to make sure we
    # properly return the module symbol tables to their original versions
    # at the end of these tests!
    io3 = parser.Filesystem(memory=True)
    try:
      # Ensure that glob has been mocked out.
      self.assertEqual('FakeGlobModule', parser.glob.__class__.__name__)

      # Create some files in our fake directory.
      for basename in ('Makefile', 'README', 'tiny.py', 'ex.meta'):
        io3.addFakeFile('testdata/' + basename, contents='blah')

      # Verify that listdir() works.
      self.assertEqual(
        ['Makefile', 'README', 'ex.meta', 'tiny.py'], io3.listdir('testdata'))

    finally:
      io3.undoMemory()
      self.assertEqual('glob', parser.glob.__name__)
      self.assertEqual('module', parser.glob.__class__.__name__)

  def test_summarize(self):
    pass

  def test_addFakeFile(self):
    pass

  def test_addFakeDir(self):
    pass

  def test_getFakeFile(self):
    pass

  def test_runClassFromMemory(self):
    pass

  def test_baseFile(self):
    io = self._io
    self.assertEqual(
      '/tmp/.meta/oopl/python/ns1/ns2.py',
      io.baseFile('/tmp/a.meta', 'ns1.ns2', self._ooplpy))
    self.assertEqual(
      '/tmp/.meta/oopl/cpp/ns1/ns2/Class.h',
      io.baseFile('/tmp/a.meta', 'ns1.ns2.Class', self._ooplcpp))
    self.assertEqual(
      '/tmp/.meta/oopl/cpp/ns1/ns2/Class.cc',
      io.baseFile('/tmp/a.meta', 'ns1.ns2.Class', self._ooplcpp, suffix='cc'))

  def test_getPublicFiles(self):
    pass


class ContextTest(TestCase):

  EXPECTED_SYNTAX = {
    'Attribute': {
      ':': {
        'Attribute': 'type',
        'abbrevs': 'List<:>',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'str'},
      '=': {
        'Attribute': 'default',
        'abbrevs': 'List<=>',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'str'},
      'Attribute': {
        'Attribute': 'Attribute',
        'default': '<required>',
        'kind': 'primary',
        'type': 'id'},
      'abbrev': {
        'Attribute': 'abbrevs',
        'abbrevs': 'List<abbrev>',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'str'},
      'abbrevs': {
        'Attribute': 'abbrevs',
        'abbrevs': 'List<abbrev>',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'str'},
      'children': {
        'Attribute': 'children',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'word-list'},
      'comment': {
        'Attribute': 'comment',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'simple'},
      'default': {
        'Attribute': 'default',
        'abbrevs': 'List<=>',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'str'},
      'implicit': {
        'Attribute': 'implicit',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'str'},
      'indent': {
        'Attribute': 'indent',
        'default': '0',
        'kind': 'secondary',
        'type': 'int'},
      'key': {
        'Attribute': 'key',
        'default': 'showkey',
        'kind': 'feature',
        'type': 'List|<nokey|showkey|abbrevkey>'},
      'kind': {
        'Attribute': 'kind',
        'default': 'undef',
        'kind': 'feature',
        'type': 'List|<undef|feature|primary|secondary>'},
      'padding': {
        'Attribute': 'padding',
        'default': 'pad_none',
        'kind': 'feature',
        'type': 'List|<pad_before|pad_after|pad_around|pad_none>'},
      'parameter': {
        'Attribute': 'parameter',
        'default': 'noparam',
        'kind': 'feature',
        'type': 'List|<noparam|multi|unique|baselang|multibaselang>'},
      'priority': {
        'Attribute': 'priority',
        'default': 'minor',
        'kind': 'feature',
        'type': 'List|<critical|major|medium|minor>'},
      'scope': {
        'Attribute': 'scope',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'complex'},
      'type': {
        'Attribute': 'type',
        'abbrevs': 'List<:>',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'str'},
      'value': {
        'Attribute': 'value',
        'default': 'showval',
        'kind': 'feature',
        'type': 'List|<noval|userval|showval>'}},
    'Construct': {
      'Construct': {
        'Attribute': 'Construct',
        'default': '<required>',
        'kind': 'primary',
        'type': 'id'},
      'associations': {
        'Attribute': 'associations',
        'abbrevs': 'List<assocs>',
        'default': '<empty>',
        'kind': 'secondary',
        'parameter': 'baselang',
        'type': 'word-list'},
      'assocs': {
        'Attribute': 'associations',
        'abbrevs': 'List<assocs>',
        'default': '<empty>',
        'kind': 'secondary',
        'parameter': 'baselang',
        'type': 'word-list'},
      'comment': {
        'Attribute': 'comment',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'simple'},
      'compile': {
        'Attribute': 'compile',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'complex'},
      'config': {
        'Attribute': 'config',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'complex'},
      'extends': {
        'Attribute': 'extends',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'xid'},
      'import': {
        'Attribute': 'imports',
        'abbrevs': 'List<import>',
        'default': '<empty>',
        'kind': 'secondary',
        'parameter': 'baselang',
        'type': 'word-list'},
      'imports': {
        'Attribute': 'imports',
        'abbrevs': 'List<import>',
        'default': '<empty>',
        'kind': 'secondary',
        'parameter': 'baselang',
        'type': 'word-list'},
      'indent': {
        'Attribute': 'indent',
        'default': '2',
        'kind': 'secondary',
        'type': 'int'},
      'level': {
        'Attribute': 'level',
        'default': 'inner',
        'kind': 'feature',
        'type': 'List|<inner|top>'},
      'metaclass': {
        'Attribute': 'metaclass',
        'default': '<metaclass>',
        'kind': 'secondary',
        'type': 'str'},
      'order': {
        'Attribute': 'order',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'id-list'},
      'padding': {
        'Attribute': 'padding',
        'default': 'pad_before',
        'kind': 'feature',
        'type': 'List|<pad_none|pad_after|pad_before|pad_around>'},
      'presence': {
        'Attribute': 'presence',
        'default': 'concrete',
        'kind': 'feature',
        'type': 'List|<concrete|abstract>'},
      'run': {
        'Attribute': 'run',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'complex'},
      'scope': {
        'Attribute': 'scope',
        'default': '<empty>',
        'kind': 'secondary',
        'parameter': 'baselang',
        'type': 'complex'},
      'special': {
        'Attribute': 'special',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'complex'},
      'template': {
        'Attribute': 'template',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'simple'},
      'test': {
        'Attribute': 'test',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'complex'}},
    'File': {
      '#': {
        'Attribute': 'comment',
        'abbrevs': 'List<#>',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'simple'},
      'File': {
        'Attribute': 'File',
        'default': '<required>',
        'kind': 'primary',
        'type': 'word'},
      'comment': {
        'Attribute': 'comment',
        'abbrevs': 'List<#>',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'simple'},
      'config': {
        'Attribute': 'config',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'complex'},
      'scope': {
        'Attribute': 'scope',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'complex'},
      'test': {
        'Attribute': 'test',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'complex'}},
    'Schema': {
      '#': {
        'Attribute': 'comment',
        'abbrevs': 'List<#>',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'simple'},
      'Schema': {
        'Attribute': 'Schema',
        'default': '<required>',
        'kind': 'primary',
        'type': 'id'},
      'comment': {
        'Attribute': 'comment',
        'abbrevs': 'List<#>',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'simple'},
      'condef': {
        'Attribute': 'condef',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'complex'},
      'config': {
        'Attribute': 'config',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'complex'},
      'file': {
        'Attribute': 'file',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'str'},
      'scope': {
        'Attribute': 'scope',
        'default': '<empty>',
        'kind': 'secondary',
        'type': 'simple'}}}

  EXPECTED_CONSINFO = {
    'Attribute': {
      'featkeys': {
        'key': 'key',
        'kind': 'kind',
        'padding': 'padding',
        'parameter': 'parameter',
        'priority': 'priority',
        'value': 'value'},
      'featvals': {
        'abbrevkey': 'key',
        'baselang': 'parameter',
        'critical': 'priority',
        'feature': 'kind',
        'major': 'priority',
        'medium': 'priority',
        'minor': 'priority',
        'multi': 'parameter',
        'multibaselang': 'parameter',
        'nokey': 'key',
        'noparam': 'parameter',
        'noval': 'value',
        'pad_after': 'padding',
        'pad_around': 'padding',
        'pad_before': 'padding',
        'pad_none': 'padding',
        'primary': 'kind',
        'secondary': 'kind',
        'showkey': 'key',
        'showval': 'value',
        'undef': 'kind',
        'unique': 'parameter',
        'userval': 'value'},
      'secondaries': {
        'abbrevs': 'abbrevs',
        'children': 'children',
        'comment': 'comment',
        'default': 'default',
        'implicit': 'implicit',
        'indent': 'indent',
        'scope': 'scope',
        'type': 'type'}},
    'Construct': {
      'featkeys': {
        'level': 'level',
        'padding': 'padding',
        'presence': 'presence'},
      'featvals': {
        'abstract': 'presence',
        'concrete': 'presence',
        'inner': 'level',
        'pad_after': 'padding',
        'pad_around': 'padding',
        'pad_before': 'padding',
        'pad_none': 'padding',
        'top': 'level'},
      'secondaries': {
        'associations': 'associations',
        'comment': 'comment',
        'compile': 'compile',
        'config': 'config',
        'extends': 'extends',
        'imports': 'imports',
        'indent': 'indent',
        'metaclass': 'metaclass',
        'order': 'order',
        'run': 'run',
        'scope': 'scope',
        'special': 'special',
        'template': 'template',
        'test': 'test'}},
    'File': {
      'featkeys': {},
      'featvals': {},
      'secondaries': {
        'comment': 'comment',
        'config': 'config',
        'scope': 'scope',
        'test': 'test'}},
    'Schema': {
      'featkeys': {},
      'featvals': {},
      'secondaries': {
        'comment': 'comment',
        'condef': 'condef',
        'config': 'config',
        'file': 'file',
        'scope': 'scope'}}}

  def stringifyDict(self, data):
    """Return an equivalent dict with all values converted to strings.

    Values that are dicts are kept as dicts, but stringifyDict is recursively
    applied on them.
    """
    # TODO(wmh): This needs to be renamed to stringifyObject, and should
    # support both dicts and lists.  Currently, if any dict value is a list,
    # it is converted to a string representation of the list, rather than
    # stringifying each of its list values.
    result = {}
    for key in data:
      value = data[key]
      if isinstance(value, dict):
        result[key] = self.stringifyDict(value)
      else:
        result[key] = str(value)
    return result

  def setUp(self):
    super(ContextTest, self).setUp()
    self._constructs = BOOTSTRAP_SCHEMA
    # TODO(wmh): Limit the number of constructs and test parent context issues.
    #self._context2 = parser.Context(self._context, self._constructs)

  def test_syntax(self):
    self.maxDiff = None
    self.assertEqual(
      ContextTest.EXPECTED_SYNTAX, self.stringifyDict(self._context.syntax()))

  def test_enders(self):
    self.assertEqual(['\n', ' ', ';'], sorted(self._context.enders()))

  def test_legal(self):
    self.assertEqual(4, len(self._context.legal()))

  def test_primaries(self):
    # TODO(wmh): Add tests to verify that primary abbrevs are also added.
    self.assertEqual(
      ['Attribute', 'Construct', 'File', 'Schema'],
      sorted(self._context.primaries().keys()))

  def test___init__(self):
    self.assertEqual(
      {'blockdent': 2,
       'cls': 'cls',
       'end': 'end',
       'field_prefix': '_',
       'field_suffix': '',
       'lsel': '<',
       'remark': '/#',
       'rsel': '>',
       'scope': ':',
       'term': ';',
       'this': 'self'},
      self._context._tokens)
    self.maxDiff = None
    self.assertEqual(
      ContextTest.EXPECTED_SYNTAX, self.stringifyDict(self._context._syntax))
    self.assertEqual(
      ContextTest.EXPECTED_CONSINFO, self._context._consinfomap)
    # TODO(wmh): There are lots of special cases in this code that need
    # to be tested (featkey/featval overlaps, etc.)

  def test_show(self):
    fp = self.fp()
    self._context.show(fp=fp)
    out = fp.getvalue()
    # TODO(wmh): Do some testing of out?  Test against a golden file?

  def test_consinfo(self):
    self.assertEqual(
      {'featkeys': {},
       'featvals': {},
       'secondaries': {'comment': 'comment',
                       'condef': 'condef',
                       'config': 'config',
                       'file': 'file',
                       'scope': 'scope'}},
      self._context.consinfo('Schema'))
    self.assertEqual(
      {'padding': 'padding', 'level': 'level', 'presence': 'presence'},
      self._context.consinfo('Construct')['featkeys'])

  def test_clone(self):
    context = self._context
    context2 = context.clone()
    self.assertEqual(context._tokens, context2._tokens)

  def test_token(self):
    self.assertEqual(':', self._context.token('scope'))
    self.assertEqual(';', self._context.token('term'))
    self.assertRaises(KeyError, self._context.token, 'blah')

  def test_re(self):
    re_type = type(re.compile(''))
    term_re = self._context.re('term')
    self.assertTrue(isinstance(term_re, re_type))
    self.assertEqual((None, None, None), term_re.match(';').groups())
    self.assertEqual(('end', None, None), term_re.match('end;').groups())
    self.assertEqual(('end', 'A', None), term_re.match('end A;').groups())
    self.assertEqual(('end', 'A', 'B'), term_re.match('end A B;').groups())
    self.assertEqual(None, term_re.match('blah'))

    aparam_re = self._context.re('aparam')
    self.assertTrue(isinstance(aparam_re, re_type))
    self.assertEqual(('C++',), aparam_re.match('<C++>').groups())
    self.assertEqual(('*',), aparam_re.match('<*>').groups())
    self.assertEqual(('',), aparam_re.match('<>').groups())
    self.assertEqual(None, aparam_re.match('blah'))

  def test__value(self):
    self.assertEqual('2', self._context._value('Construct', 'indent', 'default'))
    self.assertEqual('str', self._context._value('Attribute', 'default', 'type'))
    self.assertEqual('feature', self._context._value('Attribute', 'kind', 'kind'))

  def test_default(self):
    self.assertEqual('2', self._context.default('Construct', 'indent'))
    self.assertEqual(
      2, self._context.default('Construct', 'indent', atype='int'))

    # Verify <empty> gets converted
    self.assertEqual('', self._context.default('Attribute', 'default'))  # str
    # id-list (value is LiteralList, use data() to obtain contents).
    self.assertEqual([], self._context.default('Construct', 'order').data())
    self.assertEqual(
      [],
      self._context.default('Construct', 'comment'))  # simple
    self.assertEqual(
      [],
      self._context.default('Schema', 'config'))  # complex

    # Verify that <required> raises an exception.
    self.assertRaises(
      meta.compiler.errors.RequiredAttributeValue,
      self._context.default, 'Schema', 'Schema')

  def test_defattr(self):
    pass

  def test_cankey(self):
    self.assertEqual('default', self._context.cankey('Attribute', 'default'))
    self.assertEqual('default', self._context.cankey('Attribute', '='))
    self.assertEqual(
      'associations', self._context.cankey('Construct', 'associations'))
    self.assertEqual(
      'associations', self._context.cankey('Construct', 'assocs'))
    self.assertEqual(None, self._context.cankey('Schema', 'missing'))

  def test_type(self):
    self.assertEqual('str', self._context.type('Attribute', 'default'))
    self.assertEqual('simple', self._context.type('Construct', 'comment'))
    self.assertEqual('complex', self._context.type('Schema', 'config'))
    self.assertEqual('id-list', self._context.type('Construct', 'order'))

  def test_kind(self):
    self.assertEqual('feature', self._context.kind('Attribute', 'kind'))
    self.assertEqual('primary', self._context.kind('Attribute', 'Attribute'))
    self.assertEqual('secondary', self._context.kind('Attribute', 'scope'))

  def test_indicatesImplicitTermination(self):
    context = self._context
    for key in context._all_secondaries.keys():
      self.assertEqual(False, context.indicatesImplicitTermination(key))
    for key in context._all_featkeys.keys():
      self.assertEqual(True, context.indicatesImplicitTermination(key))
    for key in context._all_featvals.keys():
      self.assertEqual(True, context.indicatesImplicitTermination(key))
    for key in context._primaries.keys():
      self.assertEqual(True, context.indicatesImplicitTermination(key))


class LineTest(TestCase):

  def setUp(self):
    super(LineTest,self).setUp()
    self._levels = []
    self._levels2 = [0]
    self._line = parser.Line('class Person < Object:\n', 1)
    self._line2 = parser.Line('  field name: str;\n', 3)
    self._line3 = parser.Line('    \n', 17)

  def test_num(self):
    self.assertEqual(1, self._line.num())
    self.assertEqual(3, self._line2.num())
    self.assertEqual(17, self._line3.num())

  def test_indent(self):
    self.assertEqual(0, self._line.indent())
    self.assertEqual(2, self._line2.indent())
    self.assertEqual(-1, self._line3.indent())

  def test_text(self):
    self.assertEqual('class Person < Object:', self._line.text())
    self.assertEqual('field name: str;', self._line2.text())
    self.assertEqual('', self._line3.text())

  def test_line(self):
    self.assertEqual('class Person < Object:\n', self._line.line())
    self.assertEqual('  field name: str;\n', self._line2.line())
    self.assertEqual('    \n', self._line3.line())

  def test___str__(self):
    self.assertEqual('   1: class Person < Object:\n', str(self._line))
    self.assertEqual('   3:   field name: str;\n', str(self._line2))
    self.assertEqual('  17: \n', str(self._line3))

  def test_startsNewScope(self):
    self.assertEqual(True, self._line.startsNewScope(':'))
    self.assertEqual(False, self._line.startsNewScope(';'))
    self.assertEqual(False, self._line2.startsNewScope(':'))
    self.assertEqual(False, self._line3.startsNewScope(':'))


class MetaFileTest(TestCase):

  def test_context(self):
    self.assertEqual(4, len(self._context.legal()))
    context = self._parser2.context()
    self.assertEqual(13, len(context.legal()))
    self.assertTrue(isinstance(context, parser.Context))
    syntax = self._context._syntax
    for key in syntax:
      self.assertEqual(syntax[key], context._syntax[key])

  def test_filename(self):
    self.assertEqual('testdata/meta.meta', self._parser.filename())

  def test_pos(self):
    self.assertEqual(0, self._parser.pos())

  def test_lineno(self):
    self.assertEqual(1, self._parser.lineno())

  def test_streams(self):
    p = self._parser
    self.assertTrue(isinstance(p.streams(), parser.MetaStream))

  def test_construct(self):
    pass

  def test_constructIs(self):
    pass

  def test_compiler(self):
    self.assertTrue(self._metac is self._parser.compiler())

  def test_destdir(self):
    self.assertEqual('/tmp', self._parser.destdir())

  def test_parser(self):
    pass

  def test_metalang(self):
    self.assertTrue(self._oopl is self._parser.metalang())

  def test_metapath(self):
    self.assertEqual(
      '/tmp/.meta/oopl/python/meta/kernel.py',
      self._parser.metapath(self._ooplpy, subpath=['meta', 'kernel.py']))
    # TODO(wmh): Test metapath(mkdirs=True)

  def test_log(self):
    p = self._parser
    self.assertEqual([], p._logmap['order'])
    p.log('This is a test')
    self.assertEqual(
      [{'message': 'This is a test', 'line': 1, 'pos': 0, 'kind': 'I'}],
      p._logmap['order'])
    p.log('Another test', kind='E', line=17, pos=5)
    self.assertEqual(
      [{'message': 'This is a test', 'line': 1, 'pos': 0, 'kind': 'I'},
       {'message': 'Another test', 'line': 17, 'pos': 5, 'kind': 'E'}],
      p._logmap['order'])

  def test_debug(self):
    p = self._parser
    self.assertEqual([], p._logmap['order'])

    # When debug level is 0, this is a noop.
    self.assertEqual(0, p._debug_level)
    p.gotoLine(6)
    p.debug('This is a debug')
    self.assertEqual([], p._logmap['order'])

    # But when > 0, it isn't.  Furthermore, it outputs text to stdout.
    p._debug_level = 1
    self.captureStdout()
    p.debug('This is a debug')
    self.assertEqual(
      [{'message': 'This is a debug', 'line': 6, 'pos': 2, 'kind': 'D'}],
      p._logmap['order'])
    self.assertEqual('   7: This is a debug\n', self.getStdout())

  def test_info(self):
    p = self._parser
    self.assertEqual([], p._logmap['order'])
    p.gotoLine(6)
    p.info('This is an info')
    self.assertEqual(
      [{'message': 'This is an info', 'line': 6, 'pos': 2, 'kind': 'I'}],
      p._logmap['order'])

  def test_warning(self):
    p = self._parser
    self.assertEqual([], p._logmap['order'])
    p.gotoLine(6)
    p.warning('This is a warning')
    self.assertEqual(
      [{'message': 'This is a warning', 'line': 6, 'pos': 2, 'kind': 'W'}],
      p._logmap['order'])

  def test_error(self):
    p = self._parser
    self.assertEqual([], p._logmap['order'])
    p.gotoLine(5)  # blank line
    p.error('This is an error')
    self.assertEqual(
      [{'message': 'This is an error', 'line': 5, 'pos': 2, 'kind': 'E'}],
      p._logmap['order'])

  def test_fatal(self):
    p = self._parser
    self.captureStdout()
    self.assertRaises(meta.compiler.errors.Exiting, p.fatal, 'fatal!')
    self.assertMultiLineEqual("""
testdata/meta.meta:
F   1: fatal!
""", self.getStdout())

  def test_warnings(self):
    p = self._parser
    self.assertEqual(0, len(p.warnings()))
    p.warning('w1')
    p.error('e1')
    p.warning('w2')
    self.assertEqual(2, len(p.warnings()))

  def test_errors(self):
    p = self._parser
    self.assertEqual(0, len(p.errors()))
    p.warning('w1')
    p.error('e1')
    p.warning('w2')
    self.assertEqual(1, len(p.errors()))

  def test_hasErrors(self):
    p = self._parser
    self.assertFalse(p.hasErrors())
    p.warning('w1')
    self.assertFalse(p.hasErrors())
    p.error('e1')
    self.assertTrue(p.hasErrors(show=False))
    self.captureStdout()
    p.hasErrors()
    self.assertMultiLineEqual(
      '\n'
      'testdata/meta.meta:\n'
      'W   1: w1\n'
      'E   1: e1\n',
      self.getStdout())

  def test_printLog(self):
    p = self._parser
    p.log('hello')
    p.nextLine()
    p.warning('warning')
    p.gotoLine(7)
    p.error('error')
    self.captureStdout()
    p.printLog()
    self.assertMultiLineEqual("""
testdata/meta.meta:
I   1: hello
W   2: warning
E   7: error
""", self.getStdout())

  def test_die(self):
    p = self._parser
    self.captureStdout()
    p.log('hello')
    self.assertRaises(meta.compiler.errors.Exiting, p.die)
    self.assertEqual("""
testdata/meta.meta:
I   1: hello
""", self.getStdout())

  def test_showpos(self):
    p = self._parser
    self.captureStdout()
    p.showpos()
    self.assertEqual('Line 1 pos 0\n', self.getStdout())

  def test__parseFileIntoLines(self):
    p = self._parser2
    lines = p._lines
    self.assertEqual(46, len(lines))
    # TODO(wmh): More here!

  def test_getChar(self):
    p = self._parser2
    p.gotoLine(2)
    self.assertEqual('c', p.getChar())
    p.gotoLine(8)
    self.assertEqual('f', p.getChar())

  def test_nextChar(self):
    p = self._parser2
    p.gotoLine(2)
    self.assertEqual('c', p.getChar())
    self.assertEqual('l', p.nextChar())
    p.gotoLine(7)
    self.assertEqual('f', p.getChar())
    self.assertEqual('i', p.nextChar())

  def test_getLine(self):
    p = self._parser2
    self.assertEqual(1, p.getLine().num() )

  def test_nextLine(self):
    p = self._parser2
    self.assertEqual(1, p._lineno)
    self.assertEqual(0, p._pos)
    line = p.nextLine()
    self.assertEqual(2, p._lineno)
    self.assertEqual(0, p._pos)
    self.assertEqual(2, line.num())
    line = p.nextLine()
    self.assertEqual(3, line.num())
    self.assertEqual(2, p._pos)
    self.assertEqual(3, line.num())

  def test_gotoLine(self):
    p = self._parser2
    self.assertEqual(
      '   2: class Person : Object comment:\n', str(p.gotoLine(2)))
    self.assertEqual(
      '  12:   method bmi : int;\n', str(p.gotoLine(12)))
    self.assertEqual(None, p.gotoLine(100))

  def test_newLine(self):
    p = self._parser
    line = p.newLine('  this is a test')
    self.assertEqual(68, line.num())
    self.assertEqual(2, p._pos)
    self.assertTrue(p.getLine() is line)

  def test_advanceToLine(self):
    p = self._parser
    p.advanceToLine('^  Construct Schema')
    self.assertEqual(11, p.lineno())
    p.advanceToLine('^  end')
    self.assertEqual(18, p.lineno())
    p.advanceToLine('^  end')
    self.assertEqual(18, p.lineno())
    p.nextLine()
    p.advanceToLine('^  end')
    self.assertEqual(39, p.lineno())
    p.gotoLine(1)
    p.advanceToLine('^  end')
    self.assertEqual(18, p.lineno())
    p.gotoLine(1)
    p.advanceToLine('^  end', start=19)
    self.assertEqual(39, p.lineno())

  def test_advanceToParentBlock(self):
    p = self._parser

    # Move within the 'config' block of 'Construct Schema' ... parent block
    # is the 'end Construct Schema;' line.
    p.gotoLine(16)
    self.assertTrue('Attribute config' in p.getLine().line())
    line = p.advanceToParentBlock()
    self.assertEqual(18, p.lineno())
    self.assertEqual('  end Construct Schema;\n', line.line())

    line = p.advanceToParentBlock()
    self.assertEqual(67, p.lineno())
    self.assertEqual('end Schema Meta;\n', line.line())

  def test_parseConstruct(self):
    p = self._parser

    # Parse a simple one-line construct.
    line = p.gotoLine(12)
    self.assertEqual(
      '    primary            Attribute Schema                = <required>'
      '   : id;\n',
      line.line())
    construct = p.parseConstruct(indent=line.indent())
    self.assertEqual(
      'primary Attribute Schema = <required> : id;',
      construct.asStr(self._context))
    self.assertEqual(13, p.lineno())
    self.assertEqual(4, p._pos)

    # Verify that the 'indent' arg works as expected ... if we encounter a
    # line whose indent is less than the 'indent' arg, we should get back
    # None.
    line = p.gotoLine(13)
    self.assertTrue('Attribute file' in line.line())
    self.assertEqual(4, line.indent())
    self.assertEqual(None, p.parseConstruct(indent=6))
    construct = p.parseConstruct(indent=4)
    self.assertEqual('Attribute file', construct.kindid())
    construct2 = p.parseConstruct(indent=4)
    self.assertEqual('Attribute comment', construct2.kindid())
    self.assertEqual(0, len(p.errors()))
    construct3 = p.parseConstruct(indent=2)
    self.assertEqual(2, len(p.errors()))
    self.captureStdout()
    p.printLog()
    # TODO(wmh): Why is the error in Line 16 happening here?
    self.assertMultiLineEqual("""
testdata/meta.meta:
E  15: Found line indented 4 spaces when 2 spaces was expected
E  18: Found illegal token 'end' while parsing construct 'Schema': level,padding,presence
""", self.getStdout())

    # Parse a full-fledged multi-line construct (including a preamble line)
    line = p.gotoLine(11)
    self.assertEqual('  Construct Schema config:\n', line.line())
    line = p.gotoLine(10)
    self.assertEqual(-1, line.indent())
    construct = p.parseConstruct(indent=2)
    self.assertEqual(1, construct.preamble())
    self.assertMultiLineEqual("""
Construct Schema config:
  primary Attribute Schema = <required> : id;
  secondary Attribute file = <empty> : str;
  secondary Attribute comment = <empty> : simple abbrevs List<#>;
  secondary Attribute condef = <empty> : complex;
  secondary Attribute config = <empty> : complex;
  secondary Attribute scope = <empty> : simple;
end Construct Schema;""", construct.asStr(p.context()))

  def test_parseConstruct2(self):
    p2 = self._parser2
    line = p2.gotoLine(41)
    self.assertEqual('    var c : str = "hello" #:\n', line.line())
    cons = p2.parseConstruct(indent=4)
    self.assertEqual('var c', cons.kindid())

  def test_parseFile(self):
    p = self._parser

    # Parse the Meta(Meta) schema definition (meta.meta)
    file_cons = p.parseFile()
    config = file_cons.attr('config')
    self.assertEqual(4, len(config.value()))
    self.assertEqual('Schema', config.cons('Schema').id())

    # Parse a basic Meta(Oopl) program (ex.meta)
    p2 = self._parser2
    ex_file = p2.parseFile()
    self.assertEqual('File', ex_file.kind())

    # Parse the Meta(Oopl) schema definition (oopl.meta)
    filename = 'testdata/oopl.meta'
    context = parser.Context(None, BOOTSTRAP_SCHEMA)
    p3 = parser.MetaFile(filename, context)
    oopl = p3.parseFile()
    #print oopl.asStr(context)

    # Now parse the implementation of Meta written in Meta (kernel.meta).
    # However, before doing so, we parse the full Oopl schema from disk
    # rather than relying on the bootstrapped info.  This allows us to use
    # constructs added to oopl.meta that haven't been bootstrapped.
    oopl = self._metac.metalangNamed('oopl')
    oopl_schema = oopl.schema()
    context = oopl.context()
    p4 = parser.MetaFile('testdata/kernel.meta', context)
    obj = p4.parseFile()
    self.assertFalse(p4.hasErrors())

  def test_parseAttribute(self):
    p = self._parser

    # We parse all attributes of a simple construct.
    line = p.newLine('Attribute indent default 57 type int;')
    construct = p.parseToPrimary()
    self.assertEqual(16, p._pos)
    self.assertEqual('Attribute', construct.kind())
    self.assertEqual('indent', construct.id())
    attribute, tc = p.parseAttribute(construct, 0)
    self.assertEqual('default', attribute.key())
    self.assertEqual('57', attribute.value())
    self.assertEqual(27, p._pos)
    attribute2, tc = p.parseAttribute(construct, 0)
    self.assertEqual('type', attribute2.key())
    self.assertEqual('int', attribute2.value())
    self.assertEqual(36, p._pos)
    self.assertEqual(';', p._line.line()[p._pos])
    attribute3, tc = p.parseAttribute(construct, 0)
    self.assertEqual(None, attribute3)

    # Same parsing, except we use abbrevs instead of keys.
    line = p.newLine('Attribute indent = 57 : int;')
    construct = p.parseToPrimary()
    self.assertEqual(16, p._pos)
    self.assertEqual('Attribute', construct.kind())
    attribute, tc = p.parseAttribute(construct, 0)
    self.assertEqual('default', attribute.key())
    self.assertEqual('57', attribute.value())
    self.assertEqual(21, p._pos)
    attribute2, tc = p.parseAttribute(construct, 0)
    self.assertEqual('type', attribute2.key())
    self.assertEqual('int', attribute2.value())
    self.assertEqual(27, p._pos)
    self.assertEqual(';', p._line.line()[p._pos])
    attribute3, tc = p.parseAttribute(construct, 0)
    self.assertEqual(None, attribute3)

    # Now we test a situation where a construct spans multiple lines, with
    # one or more attributes on each line.
    line = p.gotoLine(1)
    self.assertEqual('Schema Meta\n', line.line())
    construct = p.parseToPrimary()
    self.assertEqual(line.num(), p.lineno())
    self.assertEqual(11, p.pos())
    attribute, tc = p.parseAttribute(construct, 0)
    self.assertEqual('file', attribute.key())
    self.assertEqual('meta.meta', attribute.value())
    self.assertEqual(line.num() + 1, p.lineno())
    self.assertEqual(14, p.pos())

    # Now to test attributes with selectors.
    p2 = self._parser2
    line = p2.gotoLine(5)
    self.assertEqual('scope<*>:\n', line.line())
    construct2 = construct
    attribute, tc = p2.parseAttribute(construct2, 0)
    self.assertEqual('*', attribute.selector())
    self.assertEqual('scope<*>', attribute.keyStr(self._context2))
    self.assertEqual(0, tc)

    # Now test implicit termination of constructs.  If we are parsing
    # secondary attributes and encounter a primary key/abbrev, feature
    # key/abbrev or feature value, we implicitly stop terminating the
    # construct.  In such situations, parseAttribute() should return
    # result of None and a termcode of -1.
    line = p2.gotoLine(38)
    self.assertEqual('    var a : int #:\n', line.line())
    cons = p2.parseToPrimary()
    self.assertEqual('var a', cons.kindid())
    res, tc = p2.parseAttribute(cons, 4)
    self.assertEqual('type', res.key())
    res, tc = p2.parseAttribute(cons, 4)
    self.assertEqual('comment', res.key())
    res, tc = p2.parseAttribute(cons, 4)
    self.assertEqual(None, res)
    self.assertEqual(-1, tc)

  def test_parseAttribute2(self):
    # More testing of implicit terminations.
    p2 = self._parser2
    line = p2.gotoLine(34)
    self.assertEqual('  get field syntax : dict #:\n', line.line())
    cons = p2.parseToPrimary()
    self.assertEqual('field syntax', cons.kindid())
    res, tc = p2.parseAttribute(cons, 2)
    self.assertEqual('type', res.key())
    res, tc = p2.parseAttribute(cons, 2)
    self.assertEqual('comment', res.key())
    res, tc = p2.parseAttribute(cons, 2)
    self.assertEqual(None, res)
    self.assertEqual(-1, tc)

    line = p2.gotoLine(37)
    self.assertEqual('  method ex2 params:\n', line.line())
    cons = p2.parseToPrimary()
    self.assertEqual('method ex2', cons.kindid())
    res, tc = p2.parseAttribute(cons, 2)
    self.assertEqual('params', res.key())
    res, tc = p2.parseAttribute(cons, 2)
    self.assertEqual('comment', res.key())

  def test_parseAttributeComplex(self):
    p = self._parser
    line = p.gotoLine(11)
    self.assertEqual('  Construct Schema config:\n', line.line())
    construct = p.parseToPrimary()
    self.assertEqual(11, p.lineno())
    self.assertEqual(18, p._pos)
    config, tc = p.parseAttribute(construct, 2)
    self.maxDiff = None
    self.assertMultiLineEqual("""
primary Attribute Schema = <required> : id;
secondary Attribute file = <empty> : str;
secondary Attribute comment = <empty> : simple abbrevs List<#>;
secondary Attribute condef = <empty> : complex;
secondary Attribute config = <empty> : complex;
secondary Attribute scope = <empty> : simple;""".lstrip(),
       '\n'.join(config.asLines(p.context())))
    newline = p.getLine()
    self.assertEqual('  end Construct Schema;\n', newline.line())
    self.assertEqual(line.indent(), newline.indent())
    self.assertEqual(18, newline.num())
    self.assertEqual(2, p.pos())

    # Now, what happens when we attempt to parse an attribute when we are
    # currently pointing at an end-of-construct terminator?
    attr, tc = p.parseAttribute(construct, 2)
    self.assertEqual(None, attr)
    self.assertEqual(15, tc)
    self.assertEqual(22, p.pos())
    self.assertEqual(18, p.lineno())

  def test_CreateNewAttribute(self):
    pass

  def test_parseToPrimary(self):
    p = self._parser
    line = p.gotoLine(30)
    self.assertTrue(line.text().startswith(
      'secondary baselang Attribute imports'))
    self.assertEqual(
      ['Attribute', 'Construct', 'File', 'Schema'],
      sorted(p.context().primaries().keys()))
    construct = p.parseToPrimary()
    self.assertEqual('Attribute imports', construct.kindid())
    self.assertEqual(
      'secondary baselang Attribute imports;', construct.asStr(p.context()))
    self.assertEqual(40, p._pos)

    p2 = self._parser2
    line = p2.gotoLine(10)
    self.assertEqual('\n', line.line())
    line = p2.gotoLine(11)
    self.assertEqual('  virtual instance\n', line.line())
    construct2 = p2.parseToPrimary()
    self.assertEqual(
      'virtual instance\nmethod bmi;', construct2.asStr(p2.context()))
    self.assertEqual(12, p2._pos)

  def test_parseSimpleBlock(self):
    p = self._parser
    line = p.gotoLine(4)
    self.assertEqual(-1, line.indent())
    block = p.parseSimpleBlock(0)
    self.assertEqual(
      ['',
       '/# Testing to see how simple blocks work. Leave spaces (unittests).',
       "The '#' at the beginning of the line is optional, and exists to make",
       '/# emacs major-mode implementation easier.',
       ''],
      block)

  def test_parseComplexBlock(self):
    p = self._parser
    line = p.gotoLine(12)
    self.assertTrue('Attribute Schema' in line.line())
    constructs = p.parseComplexBlock(2)
    self.maxDiff = None
    self.assertMultiLineEqual("""
primary Attribute Schema = <required> : id;
secondary Attribute file = <empty> : str;
secondary Attribute comment = <empty> : simple abbrevs List<#>;
secondary Attribute condef = <empty> : complex;
secondary Attribute config = <empty> : complex;
secondary Attribute scope = <empty> : simple;""".lstrip(),
      '\n'.join([cons.asStr(p.context()) for cons in constructs]))

    line = p.gotoLine(10)
    self.assertEqual(-1, line.indent())
    constructs = p.parseComplexBlock(0)
    self.assertEqual(4, len(constructs))
    self.assertEqual(1, constructs[0].preamble())
    self.assertEqual(1, constructs[1].preamble())
    self.assertEqual(1, constructs[2].preamble())

    p2 = self._parser2
    line = p2.gotoLine(29)
    self.assertEqual('  params:\n', line.line())
    self.assertEqual(2, line.indent())
    # We want to verify that an empty block is properly handled.  We are
    # parsing a block starting at line 53, but it is an empty block.
    p2.nextLine()
    block = p2.parseComplexBlock(2)
    self.assertEqual([], block)

  def test_parseComplexBlock2(self):
    p2 = self._parser2
    line = p2.gotoLine(38)
    self.assertEqual('    var a : int #:\n', line.line())
    block = p2.parseComplexBlock(2)
    self.assertEqual(3, len(block))

  def test_peekNonEmpty(self):
    p = self._parser
    line = p.gotoLine(8)
    self.assertEqual(-1, line.indent())
    line = p.peekNonEmpty()
    self.assertEqual(9, line.num())
    line = p.peekNonEmpty()
    self.assertEqual(9, line.num())
    line = p.gotoLine(40)
    self.assertEqual(-1, line.indent())
    line = p.peekNonEmpty()
    self.assertEqual(41, line.num())


class AttributeTest(TestCase):

  def setUp(self):
    super(AttributeTest,self).setUp()
    self._attr1 = parser.Attribute('kind', line=3, pos=2)
    self._attr2 = parser.Attribute('type', pos=15, line=0, litkey=':')
    self._attr3 = parser.Attribute('default', selector='python', line=0)

  def test___init__(self):
    self.assertEqual(0, self._attr2._dent)

  def test_key(self):
    self.assertEqual('kind', self._attr1.key())
    self.assertEqual('type', self._attr2.key())

  def test_litkey(self):
    self.assertEqual(None, self._attr1.litkey())
    self.assertEqual(':', self._attr2.litkey())

  def test_value(self):
    self.assertRaises(NotImplementedError, self._attr1.value)
    self.assertRaises(NotImplementedError, self._attr2.value)

  def test_valueStr(self):
    self.assertRaises(NotImplementedError, self._attr1.valueStr)
    self.assertRaises(NotImplementedError, self._attr2.valueStr)

  def test_line(self):
    self.assertEqual(3, self._attr1.line())

  def test_pos(self):
    self.assertEqual(2, self._attr1.pos())
    self.assertEqual(15, self._attr2.pos())
    self.assertEqual(None, self._attr3.pos())

  def test_dent(self):
    self.assertEqual(0, self._attr1.dent())
    self.assertEqual(0, self._attr2.dent())

  def test_selector(self):
    self.assertFalse('_selector' in self._attr2.__dict__)
    self.assertEqual(None, self._attr2.selector())
    self.assertFalse('_selector' in self._attr2.__dict__)
    self._attr2._selector = 'test'
    self.assertEqual('test', self._attr2.selector())

  def test_selectorIs(self):
    self.assertFalse('_selector' in self._attr2.__dict__)
    self.assertEqual(None, self._attr2.selector())
    self._attr2.selectorIs('test')
    self.assertEqual('test', self._attr2.selector())
    self.assertTrue('_selector' in self._attr2.__dict__)
    self._attr2.selectorIs(None)
    self.assertEqual(None, self._attr2.selector())
    self.assertFalse('_selector' in self._attr2.__dict__)

  def test_keyStr(self):
    self.assertEqual('type', self._attr2.keyStr(self._context))
    self._attr2.selectorIs('python')
    self.assertEqual('type<python>', self._attr2.keyStr(self._context))

  def test_keyLiteralStr(self):
    self.assertEqual('kind', self._attr1.keyLiteralStr())
    self.assertEqual(':', self._attr2.keyLiteralStr())
    self.assertEqual('default<python>', self._attr3.keyLiteralStr())

  def test_isFeature(self):
    self.assertEqual(False, self._attr1.isFeature())
    self.assertEqual(False, self._attr2.isFeature())

  def test_isBlock(self):
    self.assertEqual(False, self._attr1.isBlock())
    self.assertEqual(False, self._attr2.isBlock())

  def test_isComplexBlock(self):
    self.assertEqual(False, self._attr1.isComplexBlock())
    self.assertEqual(False, self._attr2.isComplexBlock())

  def test_nl(self):
    self.assertFalse(hasattr(self._attr1, '_nl'))
    self.assertEqual(False, self._attr1.nl())
    self.assertFalse(hasattr(self._attr1, '_nl'))

  def test_nlIs(self):
    self.assertFalse(hasattr(self._attr1, '_nl'))
    self.assertEqual(False, self._attr1.nl())
    self._attr1.nlIs(True)
    self.assertTrue(hasattr(self._attr1, '_nl'))
    self.assertEqual(True, self._attr1.nl())
    self._attr1.nlIs(False)
    self.assertEqual(False, self._attr1.nl())
    self.assertFalse(hasattr(self._attr1, '_nl'))


class FeatureAttributeTest(TestCase):

  def setUp(self):
    super(FeatureAttributeTest, self).setUp()
    self._fattr1 = parser.FeatureAttribute('level', 'static', line=3)

  def test___init__(self):
    self.assertEqual(0, self._fattr1._dent)

  def test_value(self):
    self.assertEqual('static', self._fattr1.value())

  def test_isFeature(self):
    self.assertEqual(True, self._fattr1.isFeature())

  def test_valueStr(self):
    self.assertEqual('static', self._fattr1.valueStr())


class ListAttributeTest(TestCase):

  def setUp(self):
    super(ListAttributeTest, self).setUp()
    self._listattr1 = parser.ListAttribute(
      'abbrevs', parser.LiteralList('List<a b c>'), line=6)
    self._listattr2 = parser.ListAttribute(
      'kinds', parser.LiteralList('List|<e|fg|hij>'), line=6)

  def test___init__(self):
    self.assertEqual(['a', 'b', 'c'], self._listattr1._value.data())
    self.assertRaises(
      meta.compiler.errors.SyntaxError,
      parser.ListAttribute, 'abbrevs', ['a', 'b', 'c'], line=1)
    self.assertRaises(
      meta.compiler.errors.SyntaxError,
      parser.ListAttribute, 'abbrevs', 'a', line=1)

  def test_value(self):
    self.assertEqual(['a', 'b', 'c'], self._listattr1.value().data())
    self.assertEqual(['e', 'fg', 'hij'], self._listattr2.value().data())

  def test_valueStr(self):
    self.assertEqual('List<a b c>', self._listattr1.valueStr())
    self.assertEqual('List|<e|fg|hij>', self._listattr2.valueStr())


class IdAttributeTest(TestCase):

  def setUp(self):
    super(IdAttributeTest, self).setUp()
    self._idattr1 = parser.IdAttribute('method', 'bmi', line=4)

  def test_Initialize(self):
    self.assertTrue('id' in parser.Context.EMPTY_TYPE_MAP)
    self.assertTrue(
      parser.IdAttribute.Empty is
      parser.Context.EMPTY_TYPE_MAP['id'])

  def test_value(self):
    self.assertEqual('bmi', self._idattr1.value())

  def test___init__(self):
    self.assertEqual(0, self._idattr1._dent)

  def test_valueStr(self):
    self.assertEqual('bmi', self._idattr1.valueStr())


class IntAttributeTest(TestCase):

  def setUp(self):
    super(IntAttributeTest, self).setUp()
    self._intattr1 = parser.IntAttribute('indent', 2, line=4)

  def test___init__(self):
    self.assertEqual(0, self._intattr1._dent)

  def test_value(self):
    self.assertEqual(2, self._intattr1.value())

  def test_valueStr(self):
    self.assertEqual('2', self._intattr1.valueStr())


class IdListAttributeTest(TestCase):

  def setUp(self):
    super(IdListAttributeTest, self).setUp()
    self._idlist1 = parser.IdListAttribute(
      'kinds', parser.LiteralList('List,<a,b,c>'), line=1)

  def test_Initialize(self):
    pass

  def test___init__(self):
    self.assertEqual(['a', 'b', 'c'], self._idlist1.value().data())


class WordAttributeTest(TestCase):

  def setUp(self):
    super(WordAttributeTest, self).setUp()
    self._wordattr1 = parser.WordAttribute('name', 'bob', line=1)

  def test_Initialize(self):
    self.assertTrue('word' in parser.Context.EMPTY_TYPE_MAP)
    self.assertTrue(
      parser.WordAttribute.Empty is
      parser.Context.EMPTY_TYPE_MAP['word'])

  def test_value(self):
    self.assertEqual('bob', self._wordattr1.value())

  def test___init__(self):
    self.assertEqual('bob', self._wordattr1._value)
    self.assertRaises(
      meta.compiler.errors.SyntaxError, parser.WordAttribute, 'name', 'alice w', line=1)

  def test_valueStr(self):
    self.assertEqual('bob', self._wordattr1.valueStr())


class WordListAttributeTest(TestCase):

  def setUp(self):
    super(WordListAttributeTest, self).setUp()
    self._wordlist1 = parser.WordListAttribute(
      'kinds', parser.LiteralList('List<a b c>'), line=1)

  def test_Initialize(self):
    self.assertTrue('word-list' in parser.Context.EMPTY_TYPE_MAP)
    self.assertTrue(
      parser.WordListAttribute.Empty is
      parser.Context.EMPTY_TYPE_MAP['word-list'])

  def test___init__(self):
    self.assertEqual(['a', 'b', 'c'], self._wordlist1.value().data())


class StrAttributeTest(TestCase):

  def setUp(self):
    super(StrAttributeTest, self).setUp()
    self._strattr1 = parser.StrAttribute('name', 'hello world', line=1)
    self._strattr2 = parser.StrAttribute('name', 'bye', line=1)
    self._strattr3 = parser.StrAttribute('name', '"hello world"', line=1)
    self._strattr4 = parser.StrAttribute('name', "'hello world'", line=1)

  def test_Initialize(self):
    self.assertTrue('str' in parser.Context.EMPTY_TYPE_MAP)
    self.assertTrue(
      parser.StrAttribute.Empty is
      parser.Context.EMPTY_TYPE_MAP['str'])

  def test_value(self):
    self.assertEqual('hello world', self._strattr1.value())
    self.assertEqual('bye', self._strattr2.value())

  def test___init__(self):
    self.assertEqual('hello world', self._strattr1._value)
    self.assertEqual('bye', self._strattr2._value)

  def test_valueStr(self):
    self.assertEqual('hello world', self._strattr1.valueStr())
    self.assertEqual('bye', self._strattr2.valueStr())
    self.assertEqual('"hello world"', self._strattr3.valueStr())
    self.assertEqual("'hello world'", self._strattr4.valueStr())

    self.assertEqual('hello world', self._strattr1.valueStr(strip=True))
    self.assertEqual('bye', self._strattr2.valueStr(strip=True))
    self.assertEqual('hello world', self._strattr3.valueStr(strip=True))
    self.assertEqual('hello world', self._strattr4.valueStr(strip=True))


class TypeAttributeTest(TestCase):

  def setUp(self):
    super(TypeAttributeTest, self).setUp()
    self._typeattr1 = parser.TypeAttribute('name', '@int', line=1)
    self._typeattr2 = parser.TypeAttribute('name', '*str', line=1)

  def test_Initialize(self):
    self.assertTrue('type' in parser.Context.EMPTY_TYPE_MAP)
    self.assertTrue(
      parser.TypeAttribute.Empty is
      parser.Context.EMPTY_TYPE_MAP['type'])

  def test___init__(self):
    self.assertEqual(
      parser.Type.Repository['@int'], self._typeattr1._value)
    self.assertEqual(
      parser.Type.Repository['*str'], self._typeattr2._value)

  def test_value(self):
    self.assertEqual(
      parser.Type.Repository['@int'], self._typeattr1._value)
    self.assertEqual(
      parser.Type.Repository['*str'], self._typeattr2._value)

  def test_valueStr(self):
    self.assertEqual('@int', self._typeattr1.valueStr())
    self.assertEqual('*str', self._typeattr2.valueStr())


class BlockAttributeTest(TestCase):

  def setUp(self):
    super(BlockAttributeTest, self).setUp()
    self._battr = parser.BlockAttribute('test', line=7)

  def test___init__(self):
    self.assertEqual('test', self._battr.key())

  def test_isBlock(self):
    self.assertEqual(True, self._battr.isBlock())


class SimpleBlockAttributeTest(TestCase):

  def setUp(self):
    super(SimpleBlockAttributeTest, self).setUp()
    self._sbattr1 = parser.SimpleBlockAttribute(
      'comment',
      ['this is a comment that spans',
       '  multiple lines.'],
      line=1)
    self._sbattr2 = parser.SimpleBlockAttribute(
      'comment',
      ['/# this is a comment that spans',
       '/#   multiple lines.'],
      line=1)

  def test_Initialize(self):
    self.assertTrue('simple' in parser.Context.EMPTY_TYPE_MAP)
    self.assertTrue(
      parser.SimpleBlockAttribute.Empty is
      parser.Context.EMPTY_TYPE_MAP['simple'])

  def test___init__(self):
    self.assertEqual('  multiple lines.', self._sbattr1._value[-1])
    self.assertRaises(
      TypeError, parser.SimpleBlockAttribute, 'comment', 'blah blah', line=1)

  def test_value(self):
    self.assertEqual('  multiple lines.', self._sbattr1.value()[-1])

  def test_size(self):
    self.assertEqual(2, self._sbattr1.size())

  def test_asStr(self):
    self.assertMultiLineEqual(
      'this is a comment that spans\n  multiple lines.\n',
      self._sbattr1.asStr(self._context))
    self.assertMultiLineEqual(
      '  this is a comment that spans\n    multiple lines.\n',
      self._sbattr1.asStr(self._context, indent=2))
    self.assertMultiLineEqual(
      '/# this is a comment that spans\n/#   multiple lines.\n',
      self._sbattr2.asStr(self._context))
    self.assertMultiLineEqual(
      '  /# this is a comment that spans\n  /#   multiple lines.\n',
      self._sbattr2.asStr(self._context, indent=2))
    self.assertMultiLineEqual(
      'this is a comment that spans\n  multiple lines.\n',
      self._sbattr2.asStr(self._context, strip_comments=True))
    self.assertMultiLineEqual(
      '    this is a comment that spans\n      multiple lines.\n',
      self._sbattr2.asStr(self._context, strip_comments=True, indent=4))

  def test_asLines(self):
    self.assertEqual(
      ['this is a comment that spans', '  multiple lines.'],
      self._sbattr1.asLines(self._context))
    self.assertEqual(
      ['  this is a comment that spans', '    multiple lines.'],
      self._sbattr1.asLines(self._context, indent='  '))


class ComplexBlockAttributeTest(TestCase):

  def setUp(self):
    super(ComplexBlockAttributeTest, self).setUp()
    self._cbattr1 = parser.ComplexBlockAttribute(
      'scope', BOOTSTRAP_SCHEMA, line=8)
    self._sbattr1 = parser.SimpleBlockAttribute(
      'comment', ['this is a comment that spans', 'multiple lines.'], line=17)

  def test_Initialize(self):
    self.assertTrue('complex' in parser.Context.EMPTY_TYPE_MAP)
    self.assertTrue(
      parser.ComplexBlockAttribute.Empty is
      parser.Context.EMPTY_TYPE_MAP['complex'])

  def test___init__(self):
    self.assertEqual(
      BOOTSTRAP_SCHEMA[0].kindid(), self._cbattr1._value[0].kindid())
    self.assertEqual(
      BOOTSTRAP_SCHEMA[0].kindid(), self._cbattr1._map['Schema'].kindid())
    self.assertRaises(
      TypeError, parser.ComplexBlockAttribute, 'scope', 'hello', line=8)

  def test_value(self):
    self.assertEqual(4, len(self._cbattr1.value()))

  def test_parent(self):
    self.assertEqual(None, self._block.parent())
    self._mcons.registerAttribute(self._block)
    self.assertTrue(self._mcons is self._block.parent())

  def test_parentIs(self):
    self.assertEqual(None, self._block.parent())
    self._block.parentIs(self._mcons)
    self.assertTrue(self._mcons is self._block.parent())

  def test_postamble(self):
    self.assertEqual(0, self._block.postamble())

  def test_postambleIs(self):
    self.assertEqual(0, self._block.postamble())
    self._block.postambleIs(7)
    self.assertEqual(7, self._block.postamble())

  def test_registerConstruct(self):
    # Since the initializer calls registerConstruct, and we've already created
    # an instance, we just test that _value and _map have been populated
    # as expected.
    self.assertEqual(4, len(self._cbattr1._value))
    self.assertEqual(4, len(self._cbattr1._map))
    self.assertTrue(self._cbattr1 is BOOTSTRAP_SCHEMA[0].parent())
    self.assertTrue(self._cbattr1 is BOOTSTRAP_SCHEMA[1].parent())
    self.assertTrue(self._cbattr1 is BOOTSTRAP_SCHEMA[2].parent())

  def test_isComplexBlock(self):
    self.assertTrue(self._cbattr1.isComplexBlock())
    self.assertFalse(self._sbattr1.isComplexBlock())

  def test_cons(self):
    attr_cons = BOOTSTRAP_SCHEMA[2]
    self.assertEqual('Attribute', attr_cons.id())
    config = attr_cons.attr('config')
    self.assertEqual(15, len(config.value()))
    self.assertEqual('value', config.cons('value').id())
    self.assertEqual(None, config.cons('missing', None))
    self.assertRaises(
      meta.compiler.errors.InvalidConstructId, config.cons, 'missing')

  def test_asLines(self):
    lines = self._cbattr1.asLines(self._context)
    self.assertEqual(
      'Construct Schema config:', lines[0])
    self.assertEqual(
      '  primary Attribute Schema default <required> type id;',  lines[1])
    self.assertEqual(
      '  secondary Attribute scope default <empty> type complex;', lines[-3])

  def test_newChild(self):
    scope = parser.ComplexBlockAttribute('scope', [], line=1)
    block = parser.SimpleBlockAttribute('scope', ['pass'], line=1, pos=3)
    method = scope.newChild(
      'method', 'func',
      feature=[parser.FeatureAttribute('level', 'static', line=1, pos=3)],
      secondary=[block], line=0, pos=0)
    self.assertMultiLineEqual(
      'static method func scope:\n'
      '  pass',
      method.asStr())


class ConstructTest(TestCase):

  def setUp(self):
    super(ConstructTest,self).setUp()
    self._attr1 = parser.FeatureAttribute('kind', 'instance', line=1)
    self._attr2 = parser.IdAttribute('Method', 'age', line=1)
    self._attr3 = parser.ComplexBlockAttribute('scope', [], line=1)

  def makeConstruct(self, selector=1):
    if selector == 1:
      cons = parser.GenericConstruct.New(
        'method', 'func', secondary=[
          parser.SimpleBlockAttribute('scope', ['var a = 1'], line=0),
          parser.SimpleBlockAttribute('scope<cpp>', ['int a = 1'], line=0),
          parser.SimpleBlockAttribute('scope<python>', [' b = 2'], line=0),
          parser.SimpleBlockAttribute('scope<C++>', ['int b = 2'], line=0),
          parser.SimpleBlockAttribute('scope<py>', ['c = 3'], line=0),
          parser.SimpleBlockAttribute('scope<c++>', ['int c = 3'], line=0),
          parser.SimpleBlockAttribute('scope<*>', ['d = 4'], line=0),
          parser.SimpleBlockAttribute('scope<cc>', ['int d = 4'], line=0),
        ], termcode=15)
    else:
      print 'makeConstruct: unknown selector %s' % selector
    return cons

  def test_Initialize(self):
    self.assertTrue('construct' in parser.Context.EMPTY_TYPE_MAP)
    self.assertTrue(
      parser.Construct.Empty is
      parser.Context.EMPTY_TYPE_MAP['construct'])

  def test_New(self):
    pass

  def test___init__(self):
    self.assertEqual('age', self._cons._id)
    self.assertEqual([], self._cons._order)
    self.assertEqual({}, self._cons._attributes)

  def test_id(self):
    self.assertEqual('age', self._cons.id())

  def test_idIs(self):
    self.assertEqual('age', self._cons.id())
    self._cons.idIs('height')
    self.assertEqual('height', self._cons.id())

  def test_attributes(self):
    self.assertEqual({}, self._cons.attributes())

  def test_order(self):
    self.assertEqual([], self._cons.order())

  def test_termcode(self):
    self.assertEqual(None, self._cons.termcode())

  def test_termcodeIs(self):
    self.assertEqual(None, self._cons.termcode())
    self._cons.termcodeIs(15)
    self.assertEqual(15, self._cons.termcode())

  def test_preamble(self):
    self.assertEqual(0, self._cons.preamble())

  def test_preambleIs(self):
    self.assertEqual(0, self._cons.preamble())
    self._cons.preambleIs(3)
    self.assertEqual(3, self._cons.preamble())

  def test_parent(self):
    self.assertEqual(None, self._cons.parent())
    self._cons.parentIs(self._block)
    self.assertTrue(self._block is self._cons.parent())

  def test_parentIs(self):
    self.assertEqual(None, self._cons.parent())
    self._cons.parentIs(self._block)
    self.assertTrue(self._block is self._cons.parent())

  def test_context(self):
    self.assertTrue(parser.BOOTSTRAP_CONTEXT is self._cons.context())

  def test_parentConstruct(self):
    # We currently don't have a parentConstruct for self._cons
    self.assertEqual(None, self._cons.parent())
    self.assertEqual(None, self._cons.parentConstruct())
    # Adding just a (complex block) parent to a construct doesn't give us
    # a parentConstruct.
    self._block.registerConstruct(self._mcons)
    self.assertTrue(self._block is self._mcons.parent())
    self.assertEqual(None, self._mcons.parentConstruct())
    # But adding a parent construct to the parent complex block does.
    self.assertEqual(None, self._block.parent())
    self._ccons.registerAttribute(self._block)
    self.assertTrue(self._ccons is self._block.parent())
    res = self._mcons.parentConstruct()
    self.assertTrue(self._ccons is self._mcons.parentConstruct())

  def test_fullid(self):
    self.assertEqual('func', self._mcons.fullid())

  def test_fullkindid(self):
    pass

  def test_idPath(self):
    self.assertEqual('func', self._mcons.idPath())

  def test_findAncestor(self):
    #self.assertEqual(None, self._mcons.findAncestor('class'))
    #self.assertTrue(self._ccons is self._ccons.findAncestor('class'))
    self._block.registerConstruct(self._mcons)
    self.assertTrue(self._block is self._mcons.parent())
    self._ccons.registerAttribute(self._block)
    self.assertTrue(self._ccons is self._block.parent())
    self.assertTrue(self._ccons is self._mcons.parentConstruct())
    self.assertTrue(self._ccons is self._mcons.findAncestor('class'))

  def test_kindid(self):
    self.assertEqual('<Unknown Construct Kind> age', self._cons.kindid())

  def test_primary(self):
    self.assertRaises(
      meta.compiler.errors.InvalidAttributeKey, self._mcons.primary)
    self._mcons.registerAttribute(self._primary)
    self.assertTrue(self._primary is self._mcons.primary())

  def test_registerAttribute(self):
    cons = self._cons
    self.assertEqual(0, len(cons._order))
    self.assertEqual(0, len(cons._attributes))
    self.assertEqual(None, self._attr3._parent)
    cons.registerAttribute(self._attr1)
    cons.registerAttribute(self._attr2)
    cons.registerAttribute(self._attr3)
    self.assertEqual(3, len(cons._order))
    self.assertEqual(3, len(cons._attributes))
    self.assertTrue(cons is self._attr3._parent)
    self.assertTrue(cons is self._attr3.parent())
    self.assertEqual(
      ['kind', 'Method', 'scope'], [a.key() for a in cons.order()])
    self.assertEqual(
      ['Method', 'kind', 'scope'], sorted(cons.attributes().keys()))

    self.assertRaises(
      meta.compiler.errors.SyntaxError, cons.registerAttribute, self._attr1)

    attr4 = parser.IdAttribute('type', 'int', selector='python', line=1)
    self.assertEqual('type<python>', attr4.keyStr())
    cons.registerAttribute(attr4)
    self.assertEqual(
      ['Method', 'kind', 'scope', 'type<python>'],
      sorted(cons.attributes().keys()))

  def test_xattr(self):
    cons = self.makeConstruct()
    #print cons.asStr()
    context = self._context2
    py_ids = self._ooplpy.allids(empty=True, meta=True)
    self.assertEqual(['py', 'python', 'Python', '', '*'], py_ids)
    metac = self._metac
    metalang = metac.metalang()
    ooplcpp = metalang.baselangNamed('C++')
    oopljava = metalang.baselangNamed('Java')
    cpp_ids = ooplcpp.allids(meta=True)
    self.assertEqual(['h', 'cc', 'cpp', 'c++', 'C++', '*'], cpp_ids)
    java_ids = oopljava.allids()
    self.assertEqual(['java', 'Java'], java_ids)
    self.assertEqual(
      'scope<py>',
      cons.xattr(
        'scope', missing=parser.EMPTY, context=context, params=py_ids).key())
    py_res = cons.xattr(
      'scope', missing=parser.EMPTY, context=context, params=py_ids, all=True)
    self.assertEqual(
      ['scope<py>', 'scope<python>', 'scope', 'scope<*>'],
      [a.key() for a in py_res])
    self.assertEqual(
      'scope<cc>',
      cons.xattr(
        'scope', missing=parser.EMPTY, context=context, params=cpp_ids).key())
    cpp_res = cons.xattr(
      'scope', missing=parser.EMPTY, context=context, params=cpp_ids, all=True)
    self.assertEqual(
      ['scope<cc>', 'scope<cpp>', 'scope<c++>', 'scope<C++>', 'scope<*>'],
      [a.key() for a in cpp_res])
    self.assertEqual(
      None,
      cons.xattr(
        'scope', missing=parser.EMPTY, context=context, params=java_ids))
    self.assertEqual(
      [],
      cons.xattr(
        'scope', missing=parser.EMPTY, context=context, params=java_ids, all=True))
    self.assertRaises(
      meta.compiler.errors.InvalidAttributeKey,
      cons.xattr,
      'scope', missing=parser.ERROR, context=context, params=java_ids)
    self.assertRaises(
      meta.compiler.errors.InvalidAttributeKey,
      cons.xattr,
      'scope', missing=parser.ERROR, context=context, params=java_ids, all=True)

  def test_xattrval(self):
    pass

  def test_attr(self):
    cons = self._cons
    cons.registerAttribute(self._attr1)
    cons.registerAttribute(self._attr2)
    self.assertEqual('kind', cons.attr('kind').key())
    self.assertEqual('Method', cons.attr('Method').key())
    self.assertRaises(
      meta.compiler.errors.InvalidAttributeKey, cons.attr, 'missing')
    empty_simple = parser.Context.EMPTY_TYPE_MAP['simple']
    self.assertTrue(empty_simple is cons.attr('missing', default=empty_simple))
    self.assertEqual('kind', cons.attr('kind', default=empty_simple).key())

    # Now something more sophisticated.
    context = self._context2
    self.assertEqual({}, context._consattrmap)
    p, filecons = self.prepare('testdata/python.meta', '/tmp')

    # Obtain a missing 'extends' attribute with default EMPTY.
    class_cons = self.find(p, filecons, 'testns.Test3')
    parent = class_cons.attr('extends', default=parser.EMPTY, context=context)
    self.assertEqual('', parent.value())
    self.assertEqual({}, context._consattrmap)

    # Obtain a missing 'extends' attribute with default LOOKUP.  In addition
    # to returning a StrAttribute, context._consattrmap should be updated.
    parent = class_cons.attr('extends', default=parser.LOOKUP, context=context)
    self.assertTrue(isinstance(parent, parser.StrAttribute))
    self.assertEqual('meta.root.Object', parent.value())
    self.assertEqual({'class': {'extends': parent}}, context._consattrmap)
    parent2 = class_cons.attr('extends', default=parser.LOOKUP, context=context)
    self.assertTrue(parent2 is parent)

    # Obtain a missing 'extends' attribute with default ERROR.
    self.assertRaises(
      meta.compiler.errors.InvalidAttributeKey, class_cons.attr,
      'extends', default=parser.ERROR, context=context)

    # Obtain a missing 'type' attribute with default EMPTY.
    var_cons = class_cons.scope().cons('func').attr('params').cons('fp')
    vtype = var_cons.attr('type', default=parser.EMPTY, context=context)
    self.assertFalse('var' in context._consattrmap)
    self.assertTrue(isinstance(vtype, parser.TypeAttribute))
    self.assertTrue(isinstance(vtype.value(), parser.Type))
    self.assertEqual('void', vtype.value().raw())

    # Obtain a missing 'type' attribute with default LOOKUP.
    vtype = var_cons.attr('type', default=parser.LOOKUP, context=context)
    self.assertTrue(isinstance(vtype, parser.TypeAttribute))
    self.assertTrue(isinstance(vtype.value(), parser.Type))
    self.assertEqual('void', vtype.value().raw())
    self.assertEqual({'type': vtype}, context._consattrmap.get('var', None))

    # Obtain a missing 'type' attribute with default ERROR.
    self.assertRaises(
      meta.compiler.errors.InvalidAttributeKey,
      var_cons.attr, 'type', default=parser.ERROR, context=context)

  def test_attrval(self):
    cons = self._cons
    cons.registerAttribute(self._attr1)
    cons.registerAttribute(self._attr2)
    self.assertEqual('instance', cons.attrval('kind'))
    self.assertEqual('age', cons.attrval('Method'))
    self.assertRaises(
      meta.compiler.errors.InvalidAttributeKey, cons.attrval, 'missing')
    empty_simple = parser.Context.EMPTY_TYPE_MAP['simple']
    self.assertEqual([], empty_simple.value())
    self.assertEqual([], cons.attrval('missing', defattr=empty_simple))
    self.assertEqual(
      'instance', cons.attrval('kind', defattr=empty_simple))

    # Now something more sophisticated.
    context = self._context2
    self.assertEqual({}, context._consattrmap)
    p, filecons = self.prepare('testdata/python.meta', '/tmp')

    class_cons = self.find(p, filecons, 'testns.Test3')
    parent = class_cons.attrval(
      'extends', defattr=parser.EMPTY, context=context)
    self.assertEqual('', parent)
    self.assertEqual({}, context._consattrmap)

    parent = class_cons.attrval(
      'extends', defattr=parser.LOOKUP, context=context)
    self.assertEqual('meta.root.Object', parent)
    self.assertEqual(['extends'], context._consattrmap.get('class', {}).keys())

    self.assertRaises(
      meta.compiler.errors.InvalidAttributeKey, class_cons.attrval,
      'extends', defattr=parser.ERROR, context=context)

    var_cons = class_cons.scope().cons('func').attr('params').cons('fp')
    vtype = var_cons.attrval('type', defattr=parser.EMPTY, context=context)
    self.assertTrue(isinstance(vtype, parser.Type))
    self.assertEqual('void', vtype.raw())

  def test_attributesMatching(self):
    pass

  def test_asLines(self):
    # TODO(wmh): Add tests for width and mode and indent.
    schema = BOOTSTRAP_SCHEMA[0]
    self.assertMultiLineEqual("""
Construct Schema config:
  primary Attribute Schema default <required> type id;
  secondary Attribute file default <empty> type str;
  secondary Attribute comment default <empty> type simple abbrevs List<#>;
  secondary Attribute condef default <empty> type complex;
  secondary Attribute config default <empty> type complex;
  secondary Attribute scope default <empty> type simple;
end Construct Schema;""".lstrip(), '\n'.join(schema.asLines(self._context)))

  def test_asStr(self):
    schema = BOOTSTRAP_SCHEMA[0]
    self.assertMultiLineEqual("""
Construct Schema config:
  primary Attribute Schema default <required> type id;
  secondary Attribute file default <empty> type str;
  secondary Attribute comment default <empty> type simple abbrevs List<#>;
  secondary Attribute condef default <empty> type complex;
  secondary Attribute config default <empty> type complex;
  secondary Attribute scope default <empty> type simple;
end Construct Schema;""".lstrip(), schema.asStr(self._context))

    p, filecons = self.prepare('testdata/python.meta', '/tmp')
    class_cons = self.find(p, filecons, 'testns.Test3')
    self.assertMultiLineEqual(
      '\n'
      'class Test3 within testns comment:\n'
      '  /# Another subclass\n'
      'scope:\n'
      '\n'
      '  native _1 scope:\n'
      '    /# A native block with some meta-level comments\n'
      '    /# in it, so we can test stripping.\n'
      '    \n'
      '    SOME_VAR = {\n'
      "      'a': 1,\n"
      "      'b': 2,\n"
      '    }\n'
      '  end native;\n'
      '\n'
      '  initializer Test3 comment:\n'
      '    /# Hi.\n'
      '  scope:\n'
      '    self._a = 1\n'
      '  end;\n'
      '\n'
      '  method func : void params:\n'
      '    var fp = {#sys.stdout#};\n'
      '  scope:\n'
      "    fp.write('hello\\n')\n"
      '  end method func;\n'
      'end class Test3;',
      class_cons.asStr())

    return
    # TODO(wmh): Is this code relevant any longer?

    config = {}
    p, filecons = self.prepare('tmp/parser.meta', '/tmp', debug_level=0)
    class_cons = self.find(p, filecons, 'parser.Logger')
    init_cons = self.find(p, filecons, 'parser.Logger.Logger')
    param_attr = init_cons.attr('params')
    context = self._context
    var = param_attr.cons('fp')
    vtypeattr = var.attr('type')
    return
    print var.asStr()
    print vtypeattr
    vtype = vtypeattr.value()
    print vtype.__class__
    print vtype
    vtype2 = var.attrval('type')
    print vtype2.__class__
    print vtype2
    print self._ooplpy.formatParams(init_cons, p)

    #init_cons.compileMeta(env, config)

  def test_dump(self):
    # TODO(wmh): Fix this!
    pass
    return
    fp = self.newStr()
    schema = BOOTSTRAP_SCHEMA[0]
    print schema.__class__
    schema.dump(fp=fp)
    print fp.getvalue()

  def test_termstr(self):
    schema = BOOTSTRAP_SCHEMA[0]
    context = self._context

    schema.termcodeIs(1)
    self.assertEqual(';', schema.termstr(context))
    schema.termcodeIs(3)
    self.assertEqual('end;', schema.termstr(context))
    schema.termcodeIs(7)
    self.assertEqual('end Construct;', schema.termstr(context))
    schema.termcodeIs(15)
    self.assertEqual('end Construct Schema;', schema.termstr(context))

  def test___str__(self):
    self.assertEqual('<Construct Schema>', str(BOOTSTRAP_SCHEMA[0]))

  def test_scope(self):
    schema = BOOTSTRAP_SCHEMA[0]
    context = self._context
    scope = schema.scope(context)
    self.assertEqual('simple', context.type('Schema', 'scope'))
    self.assertTrue(parser.Context.EMPTY_TYPE_MAP['simple'])
    scope = schema.scope(context, '')
    self.assertTrue(parser.Context.EMPTY_TYPE_MAP['simple'])
    scope = schema.scope(context, ['python', ''])
    self.assertTrue(parser.Context.EMPTY_TYPE_MAP['simple'])

    schema2 = BOOTSTRAP_OOPL[1]
    context2 = self._context2
    cls = parser.GenericConstruct('class', 'Person')
    cls.registerAttribute(parser.IdAttribute('class', 'Person', line=1))
    scope2 = parser.ComplexBlockAttribute('scope', [], line=1)
    scope3 = parser.ComplexBlockAttribute('scope<*>', [], line=1)
    scope4 = parser.ComplexBlockAttribute('scope<python>', [], line=1)
    cls.registerAttribute(scope2)
    cls.registerAttribute(scope3)
    cls.registerAttribute(scope4)
    self.assertTrue(cls.scope(context2, '*') is scope3)
    self.assertTrue(cls.scope(context2, ['', '*']) is scope2)
    self.assertTrue(cls.scope(context2, ['*', '']) is scope3)
    self.assertTrue(cls.scope(context2, ['*', 'python', '']) is scope3)
    self.assertTrue(cls.scope(context2, ['python', '', '*']) is scope4)

  def test_child(self):
    metac = parser.Compiler('oopl', 'python')
    metafile = metac.parseMeta('testdata/person.meta', debug_level=0)
    file_cons = metafile.construct()
    namespace = file_cons.child('person', kind='namespace')
    class_ = namespace.child('Person', kind='class')
    self.assertEqual('namespace person', namespace.kindid())
    self.assertEqual('class Person', class_.kindid())

  def test_span(self):
    pass


class GenericConstructTest(TestCase):

  def setUp(self):
    super(GenericConstructTest,self).setUp()
    self._schema = BOOTSTRAP_SCHEMA[0]
    self._construct = BOOTSTRAP_SCHEMA[1]
    self._attribute = BOOTSTRAP_SCHEMA[2]

  def test___init__(self):
    self.assertEqual('Construct', self._schema._kind)

  def test_kind(self):
    self.assertEqual('Construct', self._schema.kind())
    self.assertEqual('Construct', self._construct.kind())

  def test_kindid(self):
    self.assertEqual('Construct Schema', self._schema.kindid())
    self.assertEqual('Construct Construct', self._construct.kindid())

  def test_BootstrapConstruct(self):
    class_construct = BOOTSTRAP_OOPL[1]
    strify = class_construct.asStr(self._context)
    self.assertTrue(strify.startswith('Construct class config:'))
    self.assertTrue(strify.endswith('end Construct class;'))

  def test_BootstrapAttribute(self):
    attr_cons = parser.GenericConstruct.BootstrapAttribute(
      'secondary  Attribute scope  = <empty> : complex;', 2)
    self.assertEqual(
      'secondary Attribute scope default <empty> type complex;',
      attr_cons.asStr(self._context))

  def test_compileMeta(self):
    m = self.newMox()
    p, filecons = self.prepare('testdata/python.meta', '/tmp')
    baselang = self._ooplpy
    config = {}

    class_cons = self.find(p, filecons, 'testns.Test1')
    method_cons = self.find(p, filecons, 'testns.Test1.m2')
    initializer_cons = self.find(p, filecons, 'testns.Test1.Test1')
    field_cons = self.find(p, filecons, 'testns.Test1.s')
    fake_cons = parser.GenericConstruct('notakind', 'bob')

    m.StubOutWithMock(baselang, 'compile_field')
    m.StubOutWithMock(baselang, 'compile_method')
    m.StubOutWithMock(baselang, 'compile_initializer')
    m.StubOutWithMock(baselang, 'compile_class')

    baselang.compile_field(p, field_cons, config)
    baselang.compile_method(p, method_cons, config)
    baselang.compile_initializer(p, initializer_cons, config)
    baselang.compile_class(p, class_cons, config)

    m.ReplayAll()
    field_cons.compileMeta(p, baselang, config)
    method_cons.compileMeta(p, baselang, config)
    initializer_cons.compileMeta(p, baselang, config)
    class_cons.compileMeta(p, baselang, config)
    self.assertRaises(
      meta.compiler.errors.InternalError, fake_cons.compileMeta, p, baselang, config)
    m.VerifyAll()


class MetaLanguageTest(TestCase):
  # NOTE(wmh): This class will not be testable as-is in statically typed
  # languages like C++ and Java that disallow abstract classes from being
  # instantiated. We can instead create an instance of some concrete subclass
  # in setUp().

  def setUp(self):
    super(MetaLanguageTest, self).setUp()
    self._ml = parser.MetaLanguage('test', 'Test', self._metac)

  def test___init__(self):
    # noop
    self.assertEqual(None, self._ml._schema_construct)

  def test_id(self):
    self.assertEqual('test', self._ml.id())

  def test_name(self):
    self.assertEqual('Test', self._ml.name())

  def test_bases(self):
    self.assertEqual({}, self._ml.bases())

  def test_compiler(self):
    self.assertTrue(isinstance(self._ml.compiler(), parser.Compiler))

  def test_context(self):
    self.assertEqual(None, self._ml._the_context)

    # If _context is set, calling context() returns it.
    self._ml._the_context = self._ml._bootstrap
    self.assertTrue(self._ml._bootstrap is self._ml.context())

    # When _context is None, calling context() will invoke schema() to obtain
    # it.
    self._ml._the_context = None
    def MockSchema():
      obj = self._ml
      obj._the_context = self._ml._bootstrap
    orig_schema = self._ml.schema
    self._ml.schema = MockSchema
    self.assertTrue(self._ml._bootstrap is self._ml.context())
    self._ml.schema = orig_schema

  def test___str__(self):
    self.assertEqual('MetaLanguage Test', str(self._ml))

  def test_baselangNamed(self):
    self.assertEqual(None, self._ml.baselangNamed('baselang'))

  def test_registerBase(self):
    ml = self._ml
    self.assertEqual([], sorted(ml.bases().keys()))
    baselang = self._ooplpy   # parser.OoplPython(ml)
    ml.registerBase(baselang)
    self.assertEqual(
      ['Python', 'py', 'python'], sorted(ml.bases().keys()))

  def test_srcdir(self):
    self.assertEndsWith(self._ml.srcdir(), 'src/schema/test')

  def test_schema(self):
    self.assertEqual(None, self._oopl._schema_construct)
    schema = self._oopl.schema()
    self.assertTrue(schema is  self._oopl._schema_construct)
    cons_list = sorted([cons.id() for cons in schema.attrval('config')])
    self.assertEqual(
        ['Attribute', 'Construct', 'File', 'Schema',
         'accessor', 'assoc', 'class', 'field', 'initializer', 'method',
         'namespace', 'native', 'var'],
        cons_list)


class MetaMetaTest(TestCase):

  def setUp(self):
    super(MetaMetaTest, self).setUp()
    self._metameta = self._metac.metalangNamed('meta')

  def test___init__(self):
    self.assertEqual('meta', self._metameta.id())
    self.assertEqual('Meta', self._metameta.name())


class MetaOoplTest(TestCase):

  def setUp(self):
    super(MetaOoplTest, self).setUp()

  def test___init__(self):
    self.assertEqual(
      ['C++', 'Java', 'Javascript', 'Perl', 'Python',
       'c++', 'cc', 'cpp', 'h', 'java', 'javascript', 'js', 'perl', 'pm',
       'py', 'python'],
      sorted(self._oopl._bases.keys()))

  def test_func(self):
    pass


class MetaDocTest(TestCase):

  def setUp(self):
    super(MetaDocTest, self).setUp()
    self._doc = parser.MetaDoc(self._metac)

  def test___init__(self):
    self.assertEqual(
      [],
      sorted(self._doc._bases.keys()))


class BaseTestCase(TestCase):

  def setUp(self):
    super(BaseTestCase, self).setUp()
    self._ooplcpp = parser.OoplCpp(self._oopl)
    self._person = parser.GenericConstruct('class', 'Person')
    self._person.registerAttribute(parser.IdAttribute('class', 'Person', line=1))
    self._person.registerAttribute(
      parser.SimpleBlockAttribute(
        'comment', ['thist is a test', '  of a comment'], line=1))

    self._method = parser.GenericConstruct('method', 'func')
    self._method.registerAttribute(
      parser.FeatureAttribute('level', 'instance', line=1))
    self._method.registerAttribute(
      parser.IdAttribute('method', 'func', line=1))
    param_list = []
    # TODO(wmh): We need to figure out how to deal with the fact that the
    # 'default' attribute defined on 'Attribute' (and on 'var') has type 'str',
    # but the thing that it is assigned to may have a different type ... need
    # to properly do conversations to and from the string representation, or
    # find a better way to deal with this.
    for vspec in [
      {'name': 'arg1', 'type': 'int', 'comment': ['A simple int arg.']},
      {'name': 'arg2', 'type': 'str', 'comment': ['A simple str arg.']},
      {'name': 'arg3', 'type': 'int', 'default': '3',
       'comment': ['A keyword arg of type int with default value 3 that',
                   'is used to test multi-line comments']},
      {'name': 'arg4', 'type': '*Person', 'default': 'None'},
    ]:
      name = vspec['name']
      type_ = vspec['type']
      var = parser.GenericConstruct('var', vspec['name'])
      var.registerAttribute(parser.IdAttribute('var', name, line=1))
      var.registerAttribute(parser.TypeAttribute('type', type_, line=1))
      if 'default' in vspec:
        var.registerAttribute(
          parser.StrAttribute('default', vspec['default'], line=1))
      if 'comment' in vspec:
        var.registerAttribute(
          parser.SimpleBlockAttribute('comment', vspec['comment'], line=1))
      param_list.append(var)
    params = parser.ComplexBlockAttribute(
      'params', param_list, selector=self._ooplpy.id(), line=1)
    self._method.registerAttribute(params)
    scope = parser.SimpleBlockAttribute(
      'scope',
      ['if a > 0:',
       '  /# this is a comment',
       '  return a * 2'], line=1)
    self._method.registerAttribute(scope)


class BaseLanguageTest(BaseTestCase):

  def test___init__(self):
    self.assertEqual('cpp', self._ooplcpp._id)

  def test_metalang(self):
    self.assertTrue(self._ooplcpp.metalang() is self._oopl)

  def test_id(self):
    self.assertEqual('cpp', self._ooplcpp.id())

  def test_name(self):
    self.assertEqual('C++', self._ooplcpp.name())

  def test_suffixes(self):
    self.assertEqual(['h', 'cc', 'cpp', 'c++'], self._ooplcpp.suffixes())

  def test_suffix(self):
    pass

  def test___init__(self):
    pass

  def test_env(self):
    pass

  def test_envIs(self):
    pass

  def test_consattr(self):
    pass

  def test_consattributes(self):
    pass

  def test_allids(self):
    self.assertEqual(['py', 'python', 'Python'], self._ooplpy.allids())
    self.assertEqual(
      ['py', 'python', 'Python', ''], self._ooplpy.allids(empty=True))
    self.assertEqual(
      ['py', 'python', 'Python', '', '*'],
      self._ooplpy.allids(empty=True, meta=True))

  def test_config(self):
    self.assertEqual('self', self._ooplpy.config('self'))
    self.captureStdout()
    self.assertRaises(
      meta.compiler.errors.Error, self._ooplpy.config, 'not-a-config-var')

  def test_baseValue(self):
    self.assertEqual('None', self._ooplpy.baseValue('null'))
    self.assertEqual('NULL', self._ooplcpp.baseValue('null'))

  def test_fullname(self):
    self.assertEqual('Oopl<C++>', self._ooplcpp.fullname())
    self.assertEqual('Oopl<Python>', self._ooplpy.fullname())

  def test___str__(self):
    self.assertEqual('BaseLanguage C++', str(self._ooplcpp))
    self.assertEqual('BaseLanguage Python', str(self._ooplpy))

  def test_rawField(self):
    self.assertEqual('_age', self._ooplpy.rawField('age', self._context2))

  def test_metaConstruct(self):
    person = self._person
    metacons = self._ooplcpp.metaConstruct(person)
    self.assertEqual('Construct class', metacons.kindid())

  def test_getTemplate(self):
    pass

  def test_getBody(self):
    pass

  def test_instantiateTemplate(self):
    metac = parser.Compiler('oopl', 'python')
    ooplpy = self._ooplpy
    context = metac.context()

    # Faked up constructs and attributes are useful for testing the basics,
    # but make it difficult to test the line-number mapping aspects of
    # instantiateTemplate. As such, we parse person.meta and obtain various
    # constructs from within that file to test instantiateTemplate() on. We
    # can then ask for line numbers in the file and compare them with our
    # python-generated code to verify accuracy.
    metafile = metac.parseMeta('testdata/person.meta', debug_level=0)
    ooplpy.envIs(metafile)
    file_cons = metafile.construct()
    namespace = file_cons.child('person', kind='namespace')
    person_class = namespace.child('Person', kind='class')
    person_initializer = person_class.child('Person', kind='initializer')
    bmi_method = person_class.child('bmi', kind='method')
    #env = parser.FileEnv(metafile, metac, '/tmp')

    # First, we instantiate the 'bmi' method from person.Person
    bmi_scope = bmi_method.scope()
    bmi_comment_str, bmi_comment = ooplpy.docstr(context, bmi_method, indent=0)
    segment_bmi = ooplpy.instantiateTemplate(
      context, bmi_method,
      parser.VarSet([
        ('modifiers', '', bmi_method.primary()),
        ('params', 'self', bmi_method.primary()),
        ('comment', bmi_comment_str, bmi_comment),
        ('scope', bmi_scope.asLines(context), bmi_scope)]),
      template=None, debug=False)
    lines_bmi, map_bmi = segment_bmi.flattenLines()
    self.assertEqual(
      ['',
       'def bmi(self):',
       '  """Returns the body-mass index of this person.',
       '',
       '  This value is not useful for especially mesomorphic individuals.',
       '  """', '  height = self.height()',
       '  return self.weight() / (height * height)'],
       lines_bmi)
    self.assertEqual(
      [(1, 59, 'person.Person.bmi:modifiers'),
       (1, 59, 'person.Person.bmi:method'),
       (1, 59, 'person.Person.bmi:params'),
       (2, 60, 'person.Person.bmi:comment'),
       (6, 64, 'person.Person.bmi:scope')],
      map_bmi)

    # Now the same thing with the person.Person initializer.
    init_scope = person_initializer.scope()
    init_params_list, _, _, init_params = ooplpy.formatParams(
      person_initializer, metafile, level='instance')
    segment_init = ooplpy.instantiateTemplate(
      context, person_initializer,
      parser.VarSet([
        ('modifiers', '', person_initializer.primary()),
        ('params', ', '.join(init_params_list), init_params),
        ('comment', ''),
        ('scope', init_scope.asLines(context), init_scope)]),
      template=None, debug=False)
    lines_init, map_init = segment_init.flattenLines()
    self.assertEqual(
      ['',
       'def __init__(self, name=None, dob=None, weight=0.0, height=0.0):',
       '  self.nameIs(name)',
       '  self.dobIs(dob)',
       '  self.weightIs(weight)',
       '  self.heightIs(height)'],
      lines_init)
    self.assertEqual(
      [(1, 45, 'person.Person.Person:params'),
       (2, 0, 'person.Person.Person:comment'),
       (2, 50, 'person.Person.Person:scope')],
      map_init)

    # Now we instantiate a class, using as our scope the MetaSegments
    # created for the method and initializer above.
    person_comment_str, person_comment = ooplpy.docstr(
      context, person_class, indent=0)
    segment_person = ooplpy.instantiateTemplate(
      context, person_class,
      parser.VarSet([
        ('imports', ''),
        ('preamble', ''),
        ('parent', person_class.attrval('extends'), person_class.attr('extends')),
        ('comment', person_comment_str, person_comment),
        ('scope', [segment_init, segment_bmi], person_class.scope())]),
      template=None, debug=False)
    lines_person, map_person = segment_person.flattenLines()
    self.assertEqual(
      ['',
       '',
       'class Person(Meta):',
       '  """A test class.',
       '',
       '  Inserting lines or modifying existing lines will probably break various',
       '  unit tests, so it is best to add new code at the end if possible.',
       '  """',
       '',
       '  def __init__(self, name=None, dob=None, weight=0.0, height=0.0):',
       '    self.nameIs(name)',
       '    self.dobIs(dob)',
       '    self.weightIs(weight)',
       '    self.heightIs(height)',
       '',
       '  def bmi(self):',
       '    """Returns the body-mass index of this person.',
       '',
       '    This value is not useful for especially mesomorphic individuals.',
       '    """',
       '    height = self.height()',
       '    return self.weight() / (height * height)'],
      lines_person)
    self.maxDiff = None
    self.assertEqual(
      [(0, 0, 'person.Person:imports'),
       (0, 0, 'person.Person:preamble'),
       (2, 28, 'person.Person:class'),
       (2, 28, 'person.Person:parent'),
       (3, 31, 'person.Person:comment'),
       (8, 36, 'person.Person:scope'),
       (9, 45, 'person.Person.Person:params'),
       (10, 0, 'person.Person.Person:comment'),
       (10, 50, 'person.Person.Person:scope'),
       (15, 59, 'person.Person.bmi:modifiers'),
       (15, 59, 'person.Person.bmi:method'),
       (15, 59, 'person.Person.bmi:params'),
       (16, 60, 'person.Person.bmi:comment'),
       (20, 64, 'person.Person.bmi:scope')],
       map_person)

    return
    # TODO(wmh): Reinstate tmp/parser.meta or find some example method in
    # existing (or new) testdata/*.meta files that highlights wrapping
    # args.

    # Test the wrapping of long parameters.
    prs, filecons = self.prepare('tmp/parser.meta', '/tmp')
    cna = self.find(p, filecons, 'parser.MetaFile.CreateNewAttribute')
    #print cna.asStr()
    # START HERE!
    cna_args, cna_typechecks, cna_doclines, cna_params = ooplpy.formatParams(
      cna, p, level='meta')
    segment_cna = ooplpy.instantiateTemplate(
      context, cna,
      parser.VarSet([
        ('modifiers', ''),
        ('method', cna.id(), cna.primary()),
        ('params', cna_args, cna_params, ', ', 70),
        ('comment', []),
        ('scope', [], cna.scope())]),
      template=None, debug=False)
    lines_cna, map_cna = segment_cna.flattenLines()
    self.assertMultiLineEqual(
      '\n'
      'def CreateNewAttribute(cls, ckind, attr_key, attr_can_key, attr_type,\n'
      '                       attr_value, attr_key_selector, attr_line_num,\n'
      '                       attr_key_start, postamble=None):',
      '\n'.join(lines_cna))


class BaseLanguageOoplTest(BaseTestCase):

  def setUp(self):
    super(BaseLanguageOoplTest, self).setUp()
    self._blo = parser.BaseLanguageOopl(
      self._oopl, 'fake', 'Fake', ['fake'], {'rem': '>>>'})

  def test_compileMisc(self):
    pass

  def test_compile_File(self):
    # noop.  TODO(wmh): Fix this!
    pass

  def test_compile_namespace(self):
    ooplpy = self._ooplpy
    metafile, filecons = self.prepare(
      'testdata/python.meta', '/tmp', parse=False)
    self.assertEqual(None, filecons)
    constructs = metafile.parseComplexBlock(-1)
    self.assertEqual(1, len(constructs))
    namespace = constructs[0]
    self.assertEqual('namespace', namespace.kind())
    ooplpy.envIs(metafile)

    streams = metafile.streams()
    streams.initStreams('imports', 'classes')
    streams.initStreams('class-preamble')
    
    config = {}
    ooplpy.compile_namespace(metafile, namespace, config)

  def test_compile_class(self):
    pass
    # START HERE!!
    p, filecons = self.prepare('testdata/python.meta', '/tmp')
    class_cons = self.find(p, filecons, 'testns.Test1')
    #print class_cons.asStr(p.context())

  def test__compile_executable(self):
    # noop - tested by compile_method and compile_initializer
    pass

  def test_compile_method(self):
    p, filecons = self.prepare('testdata/python.meta', '/tmp')
    method_cons = self.find(p, filecons, 'testns.Test1.m2')
    streams = p.streams()
    config = {'debug': False}
    streams.initStreams('methods')
    baselang = self._ooplpy
    baselang.envIs(p)    
    baselang.compile_method(p, method_cons, config)
    self.assertEqual([], p.errors())
    self.assertMultiLineEqual(
      '\n'
      '  def m2(self, a, b=10):\n'
      '    """Two line comment describing\n'
      '    what method f1 does.\n'
      '\n'
      '    Args:\n'
      '      a: int\n'
      '      b: int\n'
      '        second (default) arg\n'
      '\n'
      '    Returns: int\n'
      '    """\n'
      '    return a * b',
      streams.flatten('methods', indent=2))
    self.assertMultiLineEqual(
      '\n'
      '    def test_m2(self):\n'
      '      self.assertEqual(30, self._test1.m2(3))\n'
      '      self.assertEqual(21, self._test1.m2(3, b=7))',
      streams.flatten('test-methods', indent=4))

  def test_compile_initializer(self):
    p, filecons = self.prepare('testdata/python.meta', '/tmp')
    streams = p.streams()
    streams.initStreams('methods')
    config = {'debug': False}
    baselang = self._ooplpy  # env.baselang()

    # Compile a simple initializer
    initializer_cons = self.find(p, filecons, 'testns.Test1.Test1')
    baselang.compile_initializer(p, initializer_cons, config)
    self.assertEqual([], p.errors())
    self.assertMultiLineEqual(
      '\n'
      '  def __init__(self, s, t):\n'
      '    """Initializer.\n'
      '\n'
      '    Args:\n'
      '      s: int\n'
      '      t: str\n'
      '    """\n'
      '    super(Test1, self).__init__()\n'
      '    self.sIs(s)\n'
      '    self.tIs(t)',
      streams.flatten('methods', indent=2))

    # Compile a child initializer with pargs.
    streams.clear()
    streams.initStreams('methods')    
    initializer2_cons = self.find(p, filecons, 'testns.Test2.Test2')
    baselang.compile_initializer(p, initializer2_cons, config)
    self.assertEqual([], p.errors())
    self.assertMultiLineEqual(
      '\n'
      '    def __init__(self, s, t, u=None):\n'
      '      """Initializer.\n'
      '\n'
      '      Args:\n'
      '        s: int\n'
      '        t: str\n'
      '        u: Test1\n'
      '      """\n'
      "      super(Test2, self).__init__(s * 10, t + ' ')\n"
      '      self._u = u',
      streams.flatten('methods', indent=4))

  def test_compile_field(self):
    p, filecons = self.prepare('testdata/python.meta', '/tmp')
    field_cons = self.find(p, filecons, 'testns.Test1.t')
    config = {'debug': False}
    streams = p.streams()
    streams.initStreams('methods', 'accessors')
    baselang = self._ooplpy
    baselang.envIs(p)
    baselang.compile_field(p, field_cons, config)
    # TODO(wmh): Add test-methods generation code to compile_field!
    self.assertMultiLineEqual(
      '\n'
      '  def t(self):\n'
      '    return self._t\n'
      '\n'
      '  def tIs(self, value):\n'
      '    self._t = value\n'
      '\n'
      '  def tRef(self):\n'
      '    return self._t',
      streams.flatten('accessors', indent=2))

  def test_compile_var(self):
    # This needs more work!
    pass

    p, filecons = self.prepare('testdata/python.meta', '/tmp')
    streams = p.streams()
    config = {'debug': False}
    baselang = self._ooplpy

    # Compile a simple initializer
    initializer_cons = self.find(p, filecons, 'testns.Test2.Test2')
    var1_cons = initializer_cons.attr('params').cons('s')
    var2_cons = initializer_cons.attr('params').cons('u')
    #print var1_cons.asStr(env.context())
    #print var2_cons.asStr(env.context())
    #baselang.compile_var(p, initializer_cons, config)

  def test_compile_native(self):
    p, filecons = self.prepare('testdata/python.meta', '/tmp')
    config = {'debug': False}
    streams = p.streams()
    streams.initStreams('methods', 'class-preamble')

    # Class-level native construct
    native_cons = self.find(p, filecons, 'testns.Test3._1')
    self._ooplpy.envIs(p)
    self._ooplpy.compile_native(p, native_cons, config)
    self.assertMultiLineEqual(
      '\n'
      '  # A native block with some meta-level comments\n'
      '  # in it, so we can test stripping.\n'
      '\n'
      '  SOME_VAR = {\n'
      "    'a': 1,\n"
      "    'b': 2,\n"
      '  }',
      streams.flatten('methods', indent=2))

    # Module-level native construct
    native_cons = self.find(p, filecons, 'testns._1')
    self._ooplpy.compile_native(p, native_cons, config)
    streams.initStreams('classes')
    self.assertMultiLineEqual(
      '\n'
      '# A module-level native block.\n'
      'VAR = 1',
      streams.flatten('class-preamble', indent=0))

  # ----------------------------------------------------------------------
  # Template methods used by compile_* to be overridden in subclasses

  def test__augmentConstruct(self):
    pass

  def test__augmentVarset(self):
    pass

  def test_compileBaseCode(self):
    pass

  def test__superSyntax(self):
    pass

  def test_docstr(self):
    blo = self._blo
    
    p, filecons = self.prepare('testdata/python.meta', '/tmp')
    cls = self.find(p, filecons, 'testns.Test1')
    m0 = cls.attr('scope').cons('m0')
    m1 = cls.attr('scope').cons('m1')
    m2 = cls.attr('scope').cons('m2')
    blo.envIs(p)

    self.assertEqual('', blo.docstr(self._context2, m0)[0])
    self.assertEqual('', blo.docstr(self._context2, m0, indent=8)[0])

    self.assertEqual(
      '>>> One line comment.', blo.docstr(self._context2, m1)[0])
    
    self.assertEqual(
      '    >>> One line comment.', blo.docstr(self._context2, m1, indent=4)[0])

    self.assertMultiLineEqual(
      '>>> Two line comment describing\n'
      '>>> what method f1 does.',
      blo.docstr(self._context2, m2)[0])
    self.assertMultiLineEqual(
      '  >>> Two line comment describing\n'
      '  >>> what method f1 does.',
      blo.docstr(self._context2, m2, indent=2)[0])

  def test_formatParams(self):
    self.assertRaises(
      NotImplementedError, self._blo.formatParams, None, None)

  def test__formatImports(self):
    self.assertRaises(
      NotImplementedError, self._blo._formatImports, None)

  def test_baseTypeToMeta(self):
    self.assertRaises(
      NotImplementedError, self._blo.baseTypeToMeta, None, None)

  def test_metaTypeToBase(self):
    self.assertRaises(
      NotImplementedError, self._blo.metaTypeToBase, None)

  def test_groupBaseFiles(self):
    pass

  # ----------------------------------------------------------------------
  # Service methods used by compile_*

  def test__compileNamespace(self):
    print 'Fix BaseLanguageOoplTest.test__compileNamespace'
    return
    
    try:
      metamap = {}
      ooplpy = self._ooplpy
      metafile, filecons = self.prepare(
        'testdata/python.meta', '/tmp', parse=False)
      self.assertEqual(None, filecons)
      constructs = metafile.parseComplexBlock(-1)
      self.assertEqual(1, len(constructs))
      namespace = constructs[0]
      self.assertEqual('namespace', namespace.kind())
      ooplpy.envIs(metafile)
      streams = metafile.streams()
      streams.initStreams('imports', 'classes', 'class-preamble')

      parser.IO = parser.Filesystem(memory=True, pathmap=metamap)
      print 'start with %s' % id(parser.IO)
      # Before we invoke compile_namespace() or _compileNamespace(), we
      # invoke _augmentConstruct.
      config = {}
      ooplpy._augmentConstruct(metafile, namespace, config)

      order = ooplpy._compileNamespace(
        metafile, namespace, test=False, comment='test comment')
    finally:
      print 'Now here with %s' % id(parser.IO)
      parser.IO.undoMemory()

  def test__loadNamespaceClassStream(self):
    p, filecons = self.prepare('testdata/python.meta', '/tmp')
    ooplpy = self._ooplpy

  def test__compileClass(self):
    pass

  def test__compileAccessor(self):
    pass

  def test__extractParams(self):
    p, filecons = self.prepare('testdata/kernel.meta', '/tmp')
    cls = self.find(p, filecons, 'kernel.A')
    m1 = cls.attr('scope').cons('instance')
    self._ooplpy.envIs(p)

    self.maxDiff = None
    res, params = self._ooplpy._extractParams(m1)
    for item in res:
      item['metatype'] = item['metatype'].raw()
      del item['param']
    self.assertEqual(
      [{'comment': ['/# A simple int arg.'],
        'metatype': '@int',
        'name': 'first_positional_argument',
        'type': 'int',
        'varg': False},
       {'comment': ['/# A simple str arg. This docstr intentionally goes on and on so that we',
                    '/# can test multi-line docs.'],
        'name': 'second_positional_argument',
        'metatype': '&str',
        'type': 'str',
        'varg': False},
       {'comment': ['/# A keyword arg. This docstr has',
                    '/#   indented text',
                    '/# so we can verify it is preserved.'],
        'default': '1',
        'metatype': '@int',        
        'name': 'first_keyword_argument',
        'type': 'int',
        'varg': False}],
      res)

  def test_createMethod(self):
    pass

  def test_createMetaRunMethod(self):
    pass

  # ----------------------------------------------------------------------
  # Miscellaneous methods.

  def test__parseEncodedImports(self):
    self.assertEqual(
      {'complex': [
        {'base': 'ak',
         'full': 'ak.bt',
         'kind': 'lib',
         'orig': 'ak!bt',
         'sub': 'bt'},
        {'base': 'd',
         'full': 'd.j',
         'kind': 'lib',
         'orig': 'd!j!k',
         'sub': 'j'},
        {'base': 'dog.ele',
         'full': 'dog.ele.fin',
         'kind': 'lib',
         'orig': 'dog.ele!fin',
         'sub': 'fin'},
        {'base': 'mt',
         'cls': 'c',
         'full': 'mt.aa.c',
         'kind': 'cls',
         'orig': 'mt!aa.c',
         'sub': 'aa'},
        {'base': 'z',
         'cls': 'd',
         'full': 'z.b.d',
         'kind': 'cls',
         'orig': 'z!b!c.d',
         'sub': 'b'}],
       'core': [
         {'base': 'mt',
          'cls': 'bb',
          'full': 'mt.bb',
          'kind': 'cls',
          'orig': 'mt!bb'},
         {'base': 'os', 'full': 'os', 'kind': 'lib', 'orig': 'os'},
         {'base': 're', 'full': 're', 'kind': 'lib', 'orig': 're'}],
       'full': [
         {'base': 'as.c', 'full': 'as.c', 'kind': 'lib', 'orig': 'as.c'},
         {'base': 'd.e.g', 'full': 'd.e.g', 'kind': 'lib', 'orig': 'd.e.g'}]},
      self._blo._parseEncodedImports(
        ['lib##os', 'lib##re',
         'lib##ak!bt', 'lib##dog.ele!fin', 'lib##d.e.g',
         'lib##d!j!k', 'lib##as.c', 
         'cls##mt!aa.c', 'cls##z!b!c.d',
         'cls##mt!bb',
         ]))

  def test_subpath(self):
    self.assertEqual('a/b/c/d/e', self._blo.subpath('a.b.c', subpaths='d/e'))
    self.assertEqual(
      'a/b/c/d/e.o', self._blo.subpath('a.b.c', subpaths='d/e', suffix='.o'))
    self.assertEqual(
      'a/b/c/d/e.o',
      self._blo.subpath('a.b.c', subpaths=['d', 'e'], suffix='.o'))
    self.assertEqual('a/b/d', self._blo.subpath('a.b.c', subpaths='../d'))
    self.assertEqual(
      'a/b/.d', self._blo.subpath('a.b.c', subpaths='../d', prefix='.'))
    self.assertEqual(
      'a/.b.tmp/d',
      self._blo.subpath('a.b', subpaths='d', ns_prefix='.', ns_suffix='.tmp'))

  def test__blockText(self):
    scope = self._method.attr('scope')
    self.assertEqual(
      '  if a > 0:\n'
      '    # this is a comment\n'
      '    return a * 2\n',
      self._ooplpy._blockText(scope, self._context, indent=2))


class OoplCppTest(BaseTestCase):

  def setUp(self):
    super(OoplCppTest, self).setUp()
    self._ooplcpp = self._oopl.baselangNamed('C++')

  def test___init__(self):
    # noop
    pass

  def test_compile_field(self):
    pass

  def test__augmentConstruct(self):
    pass

  def test__augmentVarset(self):
    pass

  def test_compileBaseCode(self):
    pass

  def test__superSyntax(self):
    pass

  def test_formatParams(self):
    pass

  def test__formatImports(self):
    pass

  def test_metaTypeToBase(self):
    cpp = self._ooplcpp
    for mts, base in (('&#str', 'const std::string&'),
                      ('@int', 'int'),
                      ('*vec<int>', 'std::vector<int>*'),
                      ('*int<32>', 'int*'),
                      ('*int<64>', 'longlong*'),
                      ('*Person', 'Person*'),
                      ('Person', 'Person*')
                      ):
      self.assertEqual(
        base, cpp.metaTypeToBase(parser.Type.Instance(mts)))

  def test_groupBaseFiles(self):
    self.assertEqual(
      [['/tmp/File.h', '/tmp/File.cc'],
       ['/tmp/FileTest.h', '/tmp/FileTest.cc'],
       ['/tmp/a.txt']],
      self._ooplcpp.groupBaseFiles(
        ['/tmp/File.cc', '/tmp/File.h',
         '/tmp/FileTest.cc', '/tmp/FileTest.h',
         '/tmp/a.txt']))

  def test__compileCode(self):
    pass


class OoplJavaTest(BaseTestCase):

  def setUp(self):
    super(OoplJavaTest, self).setUp()
    self._oopljava = self._oopl.baselangNamed('Java')

  def test___init__(self):
    # noop
    pass

  def test_compile_field(self):
    pass

  def test__augmentConstruct(self):
    pass

  def test__augmentVarset(self):
    pass

  def test__superSyntax(self):
    pass

  def test_formatParams(self):
    java = self._oopljava
    p, filecons = self.prepare('testdata/kernel.meta', '/tmp')
    cls = self.find(p, filecons, 'kernel.A')
    m1 = cls.attr('scope').cons('instance')
    java.envIs(p)
    
    args, typechecks, doclines, attribute = java.formatParams(
      m1, p, invocation=False)
    self.assertEqual(
      ['int first_positional_argument',
       'String second_positional_argument',
       'int first_keyword_argument = 1'],
      args)
    self.assertEqual([], typechecks)
    self.assertEqual([], doclines)
    self.assertEqual('params', attribute.key())

  def test__formatImports(self):
    # TODO(wmh): Verify these actually yield what is desired!
    self.assertMultiLineEqual(
      'import java.*;\n'
      'import java.lang.*;\n'
      'import java.lang;\n'
      'import java.util.*;\n'
      'import java.util.thing.*;\n'
      'import java.util.thing.that;\n'
      'import java.util.thing;\n'
      'import java.util;',
      self._oopljava._formatImports(
      ['lib##java', 'lib##java.lang',
       'lib##java!util', 'lib##java.util!thing',
       'lib##java!util!thing', 'lib##java.util!thing!that',
       'cls##java', 'cls##java.lang',
       'cls##java!util', 'cls##java.util!thing',
       'cls##java!util!thing', 'cls##java.util!thing!that',
       ]))

  def test_metaTypeToBase(self):
    java = self._oopljava
    for mts, base in (('&#str', 'String'),
                      ('@int', 'int'),
                      ('*vec', 'Vector'),
                      ('*int', 'Integer'),
                      ('*Person', 'Person'),
                      #('Person', 'Person')
                      ):
      self.assertEqual(
        base, java.metaTypeToBase(parser.Type.Instance(mts)))

  def test_groupBaseFiles(self):
    self.assertEqual(
      [['/tmp/File.java', '/tmp/FileTest.java'],
       ['/tmp/a.txt'],        
       ['/tmp/sub/Sub.java', '/tmp/sub/SubTest.java']],
      self._oopljava.groupBaseFiles(
        ['/tmp/File.java', '/tmp/FileTest.java',
         '/tmp/sub/Sub.java', '/tmp/sub/SubTest.java',
         '/tmp/a.txt']))


class OoplPythonTest(BaseTestCase):

  def test___init__(self):
    self.assertEqual(
      {'null': 'None',
       'self': 'self',
       'cls': 'cls',
       'rem': '#',
       'rem_end': None,
       'rem_start': None,
       'class_primary': False,
       'initializer_name': '__init__',
       'empty_scope_segment': ['pass'],
       'true': 'True',
       'false': 'False'},
      self._ooplpy._config_map)

  def test_compileMisc(self):
    pass

  def test_compile_field(self):
    pass

  def test__augmentConstruct(self):
    pass

  def test__augmentVarset(self):
    pass

  def test__superSyntax(self):
    pass

  def test_docstr(self):
    p, filecons = self.prepare('testdata/python.meta', '/tmp')
    cls = self.find(p, filecons, 'testns.Test1')
    m0 = cls.attr('scope').cons('m0')
    m1 = cls.attr('scope').cons('m1')
    m2 = cls.attr('scope').cons('m2')
    ooplpy = self._ooplpy
    ooplpy.envIs(p)

    self.assertFalse(ooplpy.env() is None)

    self.assertEqual('', ooplpy.docstr(self._context2, m0)[0])
    self.assertEqual('', ooplpy.docstr(self._context2, m0, indent=8)[0])

    self.assertEqual(
      '\"\"\"One line comment.\"\"\"',
      ooplpy.docstr(self._context2, m1)[0])
    self.assertEqual(
      '    \"\"\"One line comment.\"\"\"',
      ooplpy.docstr(self._context2, m1, indent=4)[0])
    field = cls.attr('scope').cons('s')

    self.assertMultiLineEqual(
      '\"\"\"Two line comment describing\n'
      'what method f1 does.\n'
      '\"\"\"',
      ooplpy.docstr(self._context2, m2)[0])
    self.assertMultiLineEqual(
      '  \"\"\"Two line comment describing\n'
      '  what method f1 does.\n'
      '  \"\"\"',
      ooplpy.docstr(self._context2, m2, indent=2)[0])

  def test_formatParams(self):
    ooplpy = self._ooplpy
    meth = self._method

    doclines = [
      'arg1: int',
      '  A simple int arg.',
      'arg2: str',
      '  A simple str arg.',
      'arg3: int',
      '  A keyword arg of type int with default value 3 that',
      '  is used to test multi-line comments',
      'arg4: *Person']

    # Verify instance methods have receiver 'self'
    self.assertEqual(
      (['self', 'arg1', 'arg2', 'arg3=3', 'arg4=None'], [], doclines),
      ooplpy.formatParams(meth, self._parser2, level='instance')[:3])

    # Verify class methods have receiver 'cls'
    self.assertEqual(
      (['cls', 'arg1', 'arg2', 'arg3=3', 'arg4=None'], [], doclines),
      ooplpy.formatParams(meth, self._parser2, level='meta')[:3])

    # Verify static methods have no receiver.
    self.assertEqual(
      (['arg1', 'arg2', 'arg3=3', 'arg4=None'], [], doclines),
      ooplpy.formatParams(meth, self._parser2, level='static')[:3])

    # Verify that the invocation parameter works.
    self.assertEqual(
      (['arg1', 'arg2', 'arg3', 'arg4'], [], doclines),
      ooplpy.formatParams(
        meth, self._parser2, invocation=True, level='instance')[:3])

    # Verify that when level=None, we get a similar effect, but with
    # keywords still passed.
    self.assertEqual(
      (['arg1', 'arg2', 'arg3=3', 'arg4=None'], [], doclines),
      ooplpy.formatParams(meth, self._parser2, level=None)[:3])

  def test__formatImports(self):
    self.assertMultiLineEqual(
      'import os\nimport re',
      self._ooplpy._formatImports(['lib##os', 'lib##re']))
    
    self.assertMultiLineEqual(
      'import mt\n'
      'import os\n'
      'import re\n'
      '\n'
      'from ak import bt\n'
      'import as.c\n'
      'from d import j\n'
      'import d.e.g\n'
      'from dog.ele import fin\n'
      'from mt import aa\n'
      'from z import b',
      self._ooplpy._formatImports(
        ['lib##os', 'lib##re',
         'lib##ak!bt', 'lib##dog.ele!fin', 'lib##d.e.g',
         'lib##d!j!k', 'lib##as.c', 
         'cls##mt!aa.c', 'cls##z!b!c.d',
         'cls##mt!bb',
         ]))

    self.assertMultiLineEqual(
      'import os\n'
      'import re\n'
      '\n'
      'from pck import mod\n'
      'from pck.apck import cls\n'
      'from pck.subpck import mod2',
      self._ooplpy._formatImports(
      'import os\n'
      'import re\n'
      'from pck import mod\n'
      'from pck.subpck import mod2\n'
      '\n'
      'import re\n'
      'from pck.apck import cls\n'
      'from pck.subpck import mod2\n'.split('\n'), formatted=True))

  def test_baseTypeToMeta(self):
    py = self._ooplpy
    self._metac._typemap = {'a': 'b'}
    self.assertEqual('b', py.baseTypeToMeta('a', self._metac))
    self.assertEqual(
      '&str', py.baseTypeToMeta('str', self._metac))
    self.assertEqual(
      '*vec<@int>', py.baseTypeToMeta('list of int', self._metac))
    self.assertEqual(
      '*vec<*vec<&str>>',
      py.baseTypeToMeta('list of list of str', self._metac))
    self.assertEqual(
      '{#some_unknown_type#}',
      py.baseTypeToMeta('some unknown type', self._metac))
    self.assertEqual(
      {'a': 1, 'list of list of str': 1, 'list of int': 1, 'int': 1,
       'list of str': 1, 'some unknown type': 1, 'str': 2},
      self._metac._typecnt)
    self.assertEqual('*Type', py.baseTypeToMeta('Type', self._metac))
    self.assertEqual(
      '*module.Type', py.baseTypeToMeta('module.Type', self._metac))
    self.assertEqual(
      '{#Type_or_str#}', py.baseTypeToMeta('Type or str', self._metac))
    # TODO(wmh): Much more testing needed

  def test_metaTypeToBase(self):
    py = self._ooplpy
    self.assertEqual('int', py.metaTypeToBase(parser.Type.Instance('@int')))
    self.assertEqual(
      'str',
      py.metaTypeToBase(parser.Type.Instance('&str', allow_invalid=True)))
    self.assertEqual(
      'Person', py.metaTypeToBase(parser.Type.Instance('*Person')))
    self.assertEqual(
      'list of str', py.metaTypeToBase(parser.Type.Instance('*vec<&str>')))
    self.assertEqual(
      'Type or str',
      py.metaTypeToBase(
        parser.Type.Instance('{#Type_or_str#}', allow_invalid=True)))

    # TODO(wmh): Must improve Type.Instance() to support nested invalid
    # types (wrap as much in legal info as possible)
    #hack = parser.Type.Instance('*vec[{#str_or_int#}]', allow_invalid=True)
    #self.assertEqual('list of str or int', py.metaTypeToBase(hack))

  def test_groupBaseFiles(self):
    self.assertEqual(
      [['/tmp/a.py', '/tmp/a_test.py'],
       ['/tmp/somefile.txt'],
       ['/tmp/sub/bcd.py', '/tmp/sub/bcd_test.py']],
      self._ooplpy.groupBaseFiles(
        ['/tmp/a.py', '/tmp/a_test.py',
         '/tmp/sub/bcd.py', '/tmp/sub/bcd_test.py',
         '/tmp/somefile.txt']))

  def test__moduleToMeta(self):
    import python
    output = python.Output()
    module_path = './testdata/kernel.py'
    self._ooplpy._moduleToMeta(self._ooplpy, output, module_path)


class OoplPerlTest(BaseTestCase):

  def setUp(self):
    super(OoplPerlTest, self).setUp()
    self._ooplperl = self._oopl.baselangNamed('Perl')

  def test___init__(self):
    # noop
    pass

  def test_compile_field(self):
    pass

  def test__augmentVarset(self):
    pass

  def test__superSyntax(self):
    pass

  def test_formatParams(self):
    pass

  def test__formatImports(self):
    self.captureStdout()
    self.assertMultiLineEqual(
      'use perl::lang;\n'
      'use perl::util;\n'
      'use perl::util::thing;\n'
      'use perl::util::thing::that;',
      self._ooplperl._formatImports(
      ['cls##', 'lib##perl.lang',
       'lib##perl!util', 'lib##perl.util!thing',
       'lib##perl!util!thing', 'lib##perl.util!thing!that',
       'cls##perl', 'cls##perl.lang',
       'cls##perl!util', 'cls##perl.util!thing',
       'cls##perl!util!thing', 'cls##perl.util!thing!that',
       ]))
    self.assertTrue('Perl cannot deal with' in self.getStdout())

  def test_metaTypeToBase(self):
    pass

  def test_groupBaseFiles(self):
    self.assertEqual(
      [['/tmp/File.pm', '/tmp/FileTest.pm'],
       ['/tmp/a.txt'],        
       ['/tmp/sub/Sub.pm', '/tmp/sub/SubTest.pm']],
      self._ooplperl.groupBaseFiles(
        ['/tmp/File.pm', '/tmp/FileTest.pm',
         '/tmp/sub/Sub.pm', '/tmp/sub/SubTest.pm',
         '/tmp/a.txt']))


class OoplJavascriptTest(BaseTestCase):

  def test___init__(self):
    # noop
    pass

  #def test_compile_class(self):
  #  pass


class MetaStreamTest(TestCase):

  def setUp(self):
    super(MetaStreamTest, self).setUp()
    self._ms = parser.MetaStream()
    self._attr1 = parser.Attribute('kind', line=3, pos=2)

  def test___init__(self):
    self.assertEqual({}, self._ms._streams)

  def test_clear(self):
    self.assertEqual([], self._ms.streamNames())
    stream1 = self._ms.stream('test1', create=True)
    stream2 = self._ms.stream('test2', create=True)
    self.assertEqual(['test1', 'test2'], self._ms.streamNames())
    self._ms.clear()
    self.assertEqual([], self._ms.streamNames())

  def test_streamNames(self):
    self.assertEqual([], self._ms.streamNames())
    stream1 = self._ms.stream('test1', create=True)
    stream2 = self._ms.stream('test2', create=True)
    self.assertEqual(['test1', 'test2'], self._ms.streamNames())

  def test_stream(self):
    self.assertEqual({}, self._ms._streams)
    stream1 = self._ms.stream('test1', create=True)
    stream2 = self._ms.stream('test2', create=True)
    stream3 = self._ms.stream('test1')
    self.assertTrue(stream3 is stream1)
    self.assertEqual(
      ['test1', 'test2'], sorted(self._ms._streams.keys()))
    stream1.append('hello\n')

    stream4 = self._ms.stream('test1')
    self.assertTrue(stream4 is stream1)
    stream5 = self._ms.stream('test1', clear=True)
    self.assertTrue(stream5 is stream1)
    self.assertEqual(['hello\n'], stream5)
    self.assertEqual([], self._ms.stream('test1'))

  def test_initStreams(self):
    pass

  def test_addLine(self):
    self.assertEqual(0, len(self._ms.stream('test1', create=True)))
    self._ms.addLine('test1', 'this is a line test')
    self.assertEqual(['this is a line test'], self._ms.stream('test1'))

  def test_addSegment(self):
    self.assertEqual(0, len(self._ms.stream('test1', create=True)))
    segment = parser.MetaSegment(['a test'])
    self._ms.addSegment('test1', segment)
    self.assertEqual(1, len(self._ms.stream('test1')))
    self.assertEqual(['a test'], self._ms.stream('test1')[0].data())

  def test_flatten(self):
    ms = self._ms
    ms.addSegment('test', parser.MetaSegment(['hello', '']), create=True)
    ms.addSegment(
      'test',
      parser.MetaSegment(
        ['staying for awhile',
         'with multiple things to say',
         '',
         'testing interior empty lines.',
         '']))
    ms.addSegment('test', parser.MetaSegment(['goodbye']))
    self.assertMultiLineEqual(
      '  hello\n'
      '\n'
      '  staying for awhile\n'
      '  with multiple things to say\n'
      '\n'
      '  testing interior empty lines.\n'
      '\n'
      '  goodbye',
      ms.flatten('test', indent=2, spaces=1))

    self.assertMultiLineEqual(
      '    hello\n'
      '\n'
      '    staying for awhile\n'
      '    with multiple things to say\n'
      '\n'
      '    testing interior empty lines.\n'
      '\n'
      '    goodbye',
      ms.flatten('test', indent=4, spaces=2))

  def test_flattenWithMap(self):
    ms = self._ms
    ms.addSegment('test', parser.MetaSegment(['hello', '']), create=True)
    ms.addSegment(
      'test',
      parser.MetaSegment(
        ['staying for awhile',
         'with multiple things to say',
         '',
         'testing interior empty lines.',
         ''],
        mapping=[(0, 10), (3, 30, 'testing')]))
    ms.addSegment('test', parser.MetaSegment(['goodbye']))
    lines, mapping = ms.flattenWithMap('test', indent=2, spaces=1)
    # NOTE(wmh): lines is tested in test_flatten() so we do not do so here.
    self.assertEqual([(2, 10), (5, 30, 'testing')], mapping)

  def test_flattenAll(self):
    # noop - used for debugging only.
    pass

  def test_dump(self):
    pass


class MetaSegmentTest(TestCase):

  def setUp(self):
    super(MetaSegmentTest, self).setUp()
    self._attr1 = parser.Attribute('method', line=10, pos=2)
    # TODO(wmh): In the interests of versimilitude, the list of lines passed
    # to MetaSegment should be python code, not meta code.
    self._segment = parser.MetaSegment(
      ['method a comment:',
       '  A comment',
       'scope:',
       '  val = 1',
       'return True'],
      mapping=[(0, 10, self._attr1)])

  def test_data(self):
    self.assertEqual(
      ['method a comment:', '  A comment', 'scope:', '  val = 1', 'return True'],
      self._segment.data())

  def test_indent(self):
    self.assertEqual('', self._segment.indent())

  def test_mapping(self):
    self.assertEqual([(0, 10, self._attr1)], self._segment.mapping())

  def test_updateIndent(self):
    self.assertEqual('', self._segment.indent())
    self._segment.updateIndent('    ')
    self.assertEqual('    ', self._segment.indent())
    self._segment.updateIndent('  ')
    self.assertEqual('      ', self._segment.indent())

  def test_flattenLines(self):
    liens, mapping = self._segment.flattenLines()
    self.assertEqual(
      [(0, 10, self._attr1)], mapping)

  def test_flattenStr(self):
    self.assertMultiLineEqual(
      'method a comment:\n'
      '  A comment\n'
      'scope:\n'
      '  val = 1\n'
      'return True',
      self._segment.flattenStr())

  def test_extendFromInterpolationData(self):
    # Start with some data
    segment = parser.MetaSegment(['Line One', ''])
    self.assertEqual(['Line One', ''], segment.data())

    # The first line of data is appended to the last line in segment.data().
    segment.extendFromInterpolationData(['Line Two', 'Line '])
    self.assertEqual(['Line One', 'Line Two', 'Line '], segment.data())

    segment.extendFromInterpolationData(['Three', 'Line Four'])
    self.assertEqual(
      ['Line One', 'Line Two', 'Line Three', 'Line Four'], segment.data())

    # If the last line in segment.data() contains whitespace that matches
    # indent, and the new data being passed in starts with a MetaSegment,
    # that MetaSegment is indented by the amount dictated by indent.
    segment.extendFromInterpolationData(['', '  '])
    subsegment = parser.MetaSegment(['Sub One', 'Sub Two', 'Sub Three'])
    self.assertEqual('', subsegment.indent())
    segment.extendFromInterpolationData([subsegment], indent='  ')
    self.assertEqual('  ', subsegment.indent())
    self.assertTrue(segment.data()[-1] is subsegment)  # not true if we clone

    # Adding a simple string to a metasegment doesn't work if the first
    # new line isn't empty...
    self.assertRaises(
      meta.compiler.errors.Error,
      segment.extendFromInterpolationData, ['more stuff', 'here'])
    # ... but works if it is empty
    segment.extendFromInterpolationData(['', 'more stuff', 'here'])
    self.assertEqual(
      ['Line One', 'Line Two', 'Line Three', 'Line Four',
       subsegment, 'more stuff', 'here'], segment.data())

  def test_serialize(self):
    pass

  def test_NewFromFile(self):
    tdir = './testdata/frozen/.frozen'
    for cname in ('Meta',):
      path = os.path.join(tdir, cname + '.py')
      mappath = os.path.join(tdir, '.' + cname + '.map')
      segment, text = parser.MetaSegment.NewFromFile(path, mappath)
      print 'NewFromFile: %s' % segment

  def test_LoadMapping(self):
    path = './testdata/frozen/.frozen/.Name.map'
    mapping = parser.MetaSegment.LoadMapping(path)
    self.maxDiff = None
    #pprint.pprint(mapping[0:27])
    self.assertEqual(
      [[0, 0, 'frozen.Name:imports', 'frozen.meta'],
       [3, 0, 'frozen.Name:preamble'],
       [4, 104, 'frozen.inter:text'],
       [6, 106, 'frozen.Name:class'],
       [6, 106, 'frozen.Name:parent'],
       [7, 107, 'frozen.Name:comment'],
       [13, 113, 'frozen.Name:scope'],
       [15, 0, 'frozen.Name.given:decorator'],
       [15, 113, 'frozen.Name.given:field'],
       [15, 0, 'frozen.Name.given:receiver'],
       [16, 0, 'frozen.Name.given:scope'],
       [18, 0, 'frozen.Name.given:decorator'],
       [18, 113, 'frozen.Name.given:field'],
       [18, 0, 'frozen.Name.given:receiver'],
       [19, 0, 'frozen.Name.given:scope'],
       [21, 0, 'frozen.Name.given:decorator'],
       [21, 113, 'frozen.Name.given:field'],
       [21, 0, 'frozen.Name.given:receiver'],
       [22, 0, 'frozen.Name.given:scope'],
       [24, 0, 'frozen.Name.family:decorator'],
       [24, 114, 'frozen.Name.family:field'],
       [24, 0, 'frozen.Name.family:receiver'],
       [25, 0, 'frozen.Name.family:scope'],
       [27, 0, 'frozen.Name.family:decorator'],
       [27, 114, 'frozen.Name.family:field'],
       [27, 0, 'frozen.Name.family:receiver'],
       [28, 0, 'frozen.Name.family:scope']],
      mapping[0:27])


class MappingTest(TestCase):

  def test___init__(self):
    pass

  def test_baseToMeta(self):
    pass


class CompilerTest(TestCase):

  def setUp(self):
    super(CompilerTest, self).setUp()
    self._metac = parser.Compiler('oopl')

  def test___init__(self):
    self.assertTrue('emacs-template' in self._metac._resources)

  def test_tokens(self):
    self.assertEqual(
      {'term': ';', 'quote_dent': ' >|', 'rem': '/#'},
      self._metac.tokens())

  def test_token(self):
    self.assertEqual(';', self._metac.token('term'))
    self.assertEqual('/#', self._metac.token('rem'))

  def test_metalang(self):
    self.assertTrue(self._metac.metalang() is self._oopl)

  def test_context(self):
    metalang = self._metac.metalang()
    self.assertTrue(metalang.context(), self._metac.context())

  def test_metalangNamed(self):
    self.assertTrue(self._metac.metalangNamed('oopl') is self._oopl)

  def test_resource(self):
    self.assertEqual(None, self._metac.resource('not-a-resource'))
    self.assertEqual(
      self._metac.metaPath('src/templates/meta-mode-template.el'),
      self._metac.resource('emacs-template'))

  def test_typemap(self):
    self.assertEqual({}, self._metac.typemap())

  def test_loadTypeMap(self):
    self._metac.loadTypeMap('./testdata/.typemap')
    self.assertEqual({'a': 'b'}, self._metac._typemap)

  def test_generateBootstraps(self):
    # This code intentionally invokes generateBootstraps() without mocks, so
    # that every time the test is run, we implicitly update the bootstrap.py
    # file.
    #
    # TODO(wmh): This may need to change latter to something more safe.
    self._metac.generateBootstraps()

  def test_generateMajorMode(self):
    # TODO(wmh): Write this to a non-standard location and verify its content
    self.captureStdout()
    self._metac.generateMajorMode()
    out = self.getStdout()
    self.assertTrue(
      'Construct  [ 13]: Attribute Construct File Schema ' in out)
    self.assertTrue(
      'Keywords   [ 12]: complex end id id-list ' in out)

  def test_parseMeta(self):
    metac = self._metac
    metalang = metac.metalang()
    baselang = metalang.baselangNamed('python')
    fp = cStringIO.StringIO()
    parser.Log.fpIs(fp)
    metafile = self._metac.parseMeta('ex/queens.meta', debug_level=0)
    #print file_cons.asStr(metafile.context())

  def test_parseMetaProgram(self):
    pass

  def test_compileMeta(self):
    pass

  def test_compileMetaProgram(self):
    files = [
      './testdata/.meta/oopl/python/person.py',
      './testdata/.meta/oopl/python/person_test.py'
    ]
    for f in files:
      if os.path.exists(f):
        os.unlink(f)
    metac = self._metac
    baselang = metac.metalang().baselangNamed('python')
    config = {}
    metafile_paths = ['testdata/person.meta']
    fp = cStringIO.StringIO()
    parser.Log.fpIs(fp)
    self.captureStdout()
    metafiles, errs = metac.parseMetaProgram(metafile_paths)
    metac.compileMetaProgram(metafiles, baselang)
    for f in files:
      self.assertTrue(os.path.exists(f))

    # Now we do it in-memory instead of on-disk.
    meta_code = """
namespace main scope:

  class Mammal scope:
  end;

  class Person : Mammal scope:
    initializer Person scope:
      pass
    end;
  end class Person;

end namespace;
"""    
    metaroot = os.getenv('METAROOT')
    self.getStdout()
    code_path = os.path.join(metaroot, 'code.meta')    
    metafile_paths = [code_path]
    metamap = {
      code_path: meta_code,
      os.path.join(metaroot, 'src', 'schema', 'meta', 'schema.meta'): None,
      os.path.join(metaroot, 'src', 'schema', 'oopl', 'schema.meta'): None,
    }
    try:
      parser.IO = parser.Filesystem(memory=True, pathmap=metamap)
      metafiles, errs = metac.parseMetaProgram(metafile_paths)
      errors = metac.compileMetaProgram(
        metafiles, baselang=baselang, config=config)
    finally:
      parser.IO.undoMemory()

    files = [
      metaroot + '/.meta/oopl/python/main.py',
      metaroot + '/.meta/oopl/python/main_test.py',
    ]
    for f in files:
      self.assertFalse(os.path.exists(f))
    src_contents = parser.IO.getFakeFile(files[0]).contents
    self.assertTrue(
      '    super(Person, self).__init__()' in src_contents)

  def test_analyzeMetaMethod(self):
    metac = self._metac
    oopl = self._oopl
    def mymethod():
      a = 1  #! params 50%
      # this test 100%
      b = 2  #! feature a 100%
      c = 2  #! feature b 50%
      d = 2
      #! feature c 0%

    progress = parser.Progress('test')
    progress.registerChild('params')
    child = progress.registerChild('feature')
    child.registerChild('a')
    child.registerChild('b')

    metac.analyzeMetaMethod(mymethod, progress)
    fp = self.fp()
    progress.show(fp=fp)
    self.assertMultiLineEqual(
      'test                : [63] \n'
      '  feature             : [75] \n'
      '    a                   : [100] \n'
      '    b                   : [50] \n'
      '  params              : [50] \n',
      fp.getvalue())

  def test_analyzeMeta(self):
    # TODO(wmh): Since moving the compile_* methods into
    # BaseLanguageOopl, this has been broken.  FIX.
    print '\nfix test_analyzeMeta'
    return
    metac = self._metac
    result = self._metac.analyzeMeta()
    fp = self.fp()
    result.show(fp=fp)
    print fp.getvalue()
    self.assertTrue('      var                 : [100] ' in fp.getvalue())

  def test_htmlProgress(self):
    fp = open('web/index.html', 'w')
    self._metac.htmlProgress(fp=fp, closure_path='closure-library')
    fp.close()


class ProgressTest(TestCase):

  def setUp(self):
    super(ProgressTest, self).setUp()
    self._p1 = parser.Progress('python')
    self._p2 = self._p1.registerChild('class', percent=50)
    self._p3 = self._p1.registerChild('method', percent=100)
    self._p4 = self._p1.registerChild('field', percent=0)
    self._p4.registerChild('field', percent=10)
    self._p4.registerChild('type', percent=20)
    self._p4.registerChild('default', percent=30)
    self._p5 = parser.Progress('noperc')

  def test___init__(self):
    self.assertEqual('python', self._p1._name)

  def test_name(self):
    self.assertEqual('python', self._p1.name())

  def test_weights(self):
    self.assertEqual(None, self._p4.weights())
    weights = {'field': 10, 'type': 1, 'default': 2, 'more': 10}
    self._p4._weights = weights
    self.assertTrue(weights is self._p4.weights())

  def test_weightsIs(self):
    self.assertEqual(None, self._p4.weights())
    weights = {'field': 10, 'type': 1, 'default': 2, 'more': 10}
    self._p4.weightsIs(weights)
    self.assertTrue(weights is self._p4.weights())

  def test_percent(self):
    self.assertEqual(50, self._p2.percent())
    self.assertEqual(0, self._p4.percent())

  def test_percentIs(self):
    self.assertEqual(0, self._p4.percent())
    self._p4.percentIs(53)
    self.assertEqual(53, self._p4.percent())

  def test_names(self):
    self.assertEqual([], self._p2.names())
    self.assertEqual(['class', 'field', 'method'], self._p1.names())

  def test_child(self):
    self.assertTrue(self._p2 is self._p1.child('class'))
    self.assertTrue(self._p3 is self._p1.child('method'))
    self.assertEqual(None, self._p1.child('not-a-child'))

  def test_registerChild(self):
    # Called in setUp()
    self.assertEqual(3, len(self._p1._children))
    self.assertRaises(
      meta.compiler.errors.Error, self._p1.registerChild, 'field', percent=10)

  def test_computePercent(self):
    self.assertEqual(50, self._p2.computePercent())
    self.assertEqual(100, self._p3.computePercent())
    self.assertEqual(20, self._p4.computePercent())
    self.assertEqual(57, self._p1.computePercent())

    self._p4._weights = {'field': 10, 'type': 1, 'default': 2, 'more': 10}
    # 10 * (10/23) + 20 * (1/23) + 30 * (2/23) = 7.826 = 8
    self.assertEqual(8, self._p4.computePercent())

  def test_show(self):
    fp = self.fp()
    self._p3.show(fp=fp)
    self.assertEqual('method              : [100] \n', fp.getvalue())

    fp = self.fp()
    self._p4.show(fp=fp)
    self.assertMultiLineEqual(
      'field               : [0] \n'
      '  default             : [30] \n'
      '  field               : [10] \n'
      '  type                : [20] \n',
      fp.getvalue())

    fp = self.fp()
    self._p1.show(fp=fp)
    self.assertMultiLineEqual(
      'python              : [57] \n'
      '  class               : [50] \n'
      '  field               : [0] \n'
      '    default             : [30] \n'
      '    field               : [10] \n'
      '    type                : [20] \n'
      '  method              : [100] \n',
      fp.getvalue())

  def test_html(self):
    metac = self._metac
    oopl_progress = metac.analyzeMeta()
    fp = open('web/index.html', 'w')
    oopl_progress.html(
      fp=fp, wrap=True, title='Oopl', colrow='BaseLangs-><br>Constructs')
    for cons_name in oopl_progress.names():
      cons_progress = oopl_progress.child(cons_name)
      cons_progress.html(
        fp=fp, title='Construct %s' % cons_name,
        colrow='Construct<br>Attribute')
    fp.write('  </body>\n')
    fp.write('</html>\n')
    fp.close()


if __name__ == '__main__':
  # logging.basicConfig(level=logging.INFO)
  testing.main()

